正常情况下,如果访问发生异常错误,Spring Boot自动配置的默认错误处理器会查找名为error的视图,如果找不到就用默认的白标错误视图,如下图所示。

如果我们在/templates/error目录下预先放置了404.html 500.html等错误静态页面,Springboot会自动进行对应跳转,显得十分便利。

而 为了自定义错误界面,我们可能需要对Controller类产生的异常进行拦截,在此我们使用到了两个注解:@ControllerAdvice @ExceptionHandler

@ControllerAdvice注解

@ControllerAdvice是控制器增强注解。可以用于定义@ExceptionHandler@InitBinder@ModelAttribute,并应用到所有@RequestMapping中。
启动应用后,被 @ExceptionHandler@InitBinder@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 注解的方法上。
@ExceptionHandler 拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler 配置的 value 指定需要拦截的异常类型,下面拦截了 Exception.class 这种异常。

代码实例

全局处理

Handler类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.ray.blog.handler;

import lombok.extern.log4j.Log4j2;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
@Log4j2
public class ControllerExceptionHandler {
@ExceptionHandler(Exception.class) //拦截所有Exception类
public ModelAndView exceptionHandler(HttpServletRequest httpServletRequest, Exception e) throws Exception {
log.error("find exception:e={}",e.getMessage()); //log4j2在日志中打印错误信息
ModelAndView mv = new ModelAndView();
//将访问信息和异常写入参数中
mv.addObject("url", httpServletRequest.getRequestURL());
mv.addObject("exception", e);
//重定向到error/error中
mv.setViewName("error/error");
return mv;
}
}

我们故意将Controller类中加入报错代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ray.blog.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {
@GetMapping("/")
public String index(){
int a = 7/0; //报错代码
return "index";
}
}

访问127.0.0.1:8080后,产生异常,随后被拦截重定向到error页面,达到了我们的预期效果
为了调试方便,我们将error.html套用了Thymeleaf,以注释的方式将具体异常信息注入到网页中

页面如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>error</title>
</head>
<body>
<h1>error</h1>
<div th:utext="${exception.message}"></div>
<!--以注释的形式将异常相关内容打印在网页中-->
<div>
<div th:utext="'&lt;!--'" th:remove="tag"></div>
<div th:utext="'Failed Request URL: ' + ${url}" th:remove="tag"></div>
<div th:utext="'ExceptionMessage: ' + ${exception.message}" th:remove="tag"></div>
<ul th:remove="tag">
<li th:each="st : ${exception.stackTrace}" th:remove="tag">
<span th:utext="${st}" th:remove="tag"></span>
</li>
</ul>
<div th:utext="'--&gt;'" th:remove="tag"></div>
</div>
</body>
</html>

效果如下:

日志信息:

1
2
3
4
21:07:40.673 [http-nio-8080-exec-8] ERROR com.ray.blog.handler.ControllerExceptionHandler 
- find exception:e=/ by zero
21:07:40.673 [http-nio-8080-exec-8] WARN org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
- Resolved [java.lang.ArithmeticException: / by zero]

页面:

具体异常信息以注释方式被注入:

显然通过@ExceptionHandler的多重组合,我们可以应用于不同异常的拦截,分别设置重定向的页面

使用注解处理自定义异常

假设我们自定义异常NotFound:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ray.blog;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
//使用RsponseStatus注解标注
public class NotFoundException extends RuntimeException {
public NotFoundException(){
}
public NotFoundException(String message){
super(message);
}
public NotFoundException(String message, Throwable cause){
super(message, cause);
}
}

Controller类中特意加入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ray.blog.web;
import com.ray.blog.NotFoundException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
public String index(){
String blog = null;
if(blog == null){
throw new NotFoundException("博客不存在");
}
return "index";
}
}

我们在Handler类中加入额外两行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.ray.blog.handler;

import lombok.extern.log4j.Log4j2;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;


@ControllerAdvice
@Log4j2
public class ControllerExceptionHandler {
@ExceptionHandler(Exception.class)
public ModelAndView exceptionHandler(HttpServletRequest httpServletRequest, Exception e) throws Exception {
log.error("find exception:e={}",e.getMessage());
if(AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null){
//若异常对应注解包含ResponseStatus,直接将异常抛出
throw e;
}
//否则跳转到error.html
ModelAndView mv = new ModelAndView();
mv.addObject("url", httpServletRequest.getRequestURL());
mv.addObject("exception", e);
mv.setViewName("error/error");
return mv;
}
}

随后我们对127.0.0.1:8080进行访问,会抛出我们自定义的异常,并由Springboot直接跳转到静态的404页面。