Spring Boot 2系列(三十八):全局异常统一处理
Spring Boot 在默认情况下,提供了 /error
映射来处理所有错误,在 Servlet 容器里注册了全局的错误页面(Whitelabel Error Page)并返回客户端。
也可以自定义替换默认的异常处理, 通过实现 ErrorController 接口并注册为 Bean;或者添加 ErrorAttributes 类型的 Bean 来替换内容。
通常情况下,都会自定义全局异常统一处理,返回统一的消息结构体,便于了解和快速定位问题,Spring 提供了 @ControllerAdvice 注解,非常好实现。
Spring Boot 官方文档,错误处理:28.1.11 Error Handling
Spring Boot 自动配置还提供了实现 ErrorController 接口异常处理的基类 BasicErrorController,默认是处理 text/html类型请求的错误,可以继承该基类自定义处理更多的请求类型,添加公共方法并使用 @RequestMapping 注解的 produce属性指定处理类型。
还可以定义一个使用 @ControllerAdvice 注解的类,返回指定控制器的指定的异常类型的 JSON 格式的消息。
可了解 SpringMVC注解之@ControllerAdvice ,本篇示例下 @ControllerAdvice 注解 Controller 层异常的全局处理。
错误消息
API 接口项目,异常处理统一返回 JSON 格式的消息。
- 统一消息结构体
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/**
* @desc: 统一响应消息结构体
* @date: 2019/1/30 11:26
**/
public class ResultBean implements Serializable {
private static final long serialVersionUID = -8332309757143905140L;
private static final boolean SUCCESS = true;
private static final boolean FAIL = false;
private Boolean state;
private Integer code;
private String msg;
private Object data;
public ResultBean successResult(){
this.state = SUCCESS;
return this;
}
public ResultBean failResult(){
this.state = FAIL;
return this;
}
//.....set/get方法......
} - 全局异常处理类 另一个示例:
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/**
* @desc: 全局异常处理
* @date: 2019/1/30 10:29
**/
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
public ResultBean defaultErrorHandler(HttpServletRequest req, Exception e) {
logger.error("", e);
ResultBean resultBean = new ResultBean();
resultBean.setMsg(e.getMessage());
if (e instanceof NoHandlerFoundException) {
resultBean.setCode(404);
} else {
resultBean.setCode(500);
}
resultBean.setState(false);
resultBean.setData(null);
return resultBean;
}
}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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46/**
* @desc: 全局异常处理
**/
public class GlobalExceptionHandler {
private Logger logger = LogManager.getLogger(GlobalExceptionHandler.class);
public ResponseModel defaultErrorHandler(HttpServletRequest req, Exception e) {
ResponseModel<?> repsData = ResponseModel.fail();
if (e instanceof DuplicateKeyException) {
//唯一索引重复
repsData = ResponseModel.duplicateKey();
} else if (e instanceof NoHandlerFoundException) {
//找不到处理器(访问的URI不存在)
repsData = ResponseModel.uriNotFound();
} else if (e instanceof HttpRequestMethodNotSupportedException) {
//HTTP请求类型不支持
repsData = ResponseModel.methodNotSupport();
} else if (e instanceof HttpMessageNotReadableException) {
//请求体不能为空
repsData = ResponseModel.reqBodyCantBeEmpty();
} else if (e instanceof DataIntegrityViolationException) {
//当尝试插入或更新数据导致违反完整性约束时引发异常。
repsData = ResponseModel.reqParamLengthOutOfRange();
} else if (e instanceof MethodArgumentNotValidException) {
//方法参数校验异常
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
if (bindingResult.hasErrors()) {
List<ObjectError> errorList = bindingResult.getAllErrors();
for (ObjectError error : errorList) {
return ResponseModel.reqParamTypeError(error.getDefaultMessage());
}
}
} else if (e instanceof MaxUploadSizeExceededException) {
// 上传的文件大小超出最大限制
return ResponseModel.uploadFileSizeOutOfMaxLimit();
}
logger.warn("统一异常处理:{}", LogFormatUtil.exceptionMsgFormat(e));
return repsData;
}
} - 在 application.properties 添加配置
Spring MVC 开启抛出找不到映射处理的异常,关闭静态资源映射1
2spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false - 官方示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
错误页面
还可以根据状态码来自定义 HTML 错误页面,将页面文件添加静态资源的 error
目录。
错误页面可以是静态 HTML (可以添加到静态资源目录),也可以使用模板构建。文件名须以 HTTP 状态码或系列掩码命名(如:5xx)。
- 示例:404 静态 HTML 页面
1
2
3
4
5
6
7
8
9src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets> - 示例:系列掩码命名模板文件
1
2
3
4
5
6
7
8
9src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftl
+- <other templates> - 对于列复杂的映射,还可以添加实现 ErrorViewResolver 接口的 Bean
1
2
3
4
5
6
7
8
9public class MyErrorViewResolver implements ErrorViewResolver {
public ModelAndView resolveErrorView(HttpServletRequest request,
HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
return ...
}
}
相关参考
Spring Boot 2系列(三十八):全局异常统一处理
http://blog.gxitsky.com/2019/01/30/SpringBoot-38-global-exception-handle/