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. 统一消息结构体
    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方法......
    }
  2. 全局异常处理类
    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
    **/
    @ControllerAdvice
    public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    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: 全局异常处理
    **/
    @ControllerAdvice
    public class GlobalExceptionHandler {
    private Logger logger = LogManager.getLogger(GlobalExceptionHandler.class);

    @ResponseBody
    @ExceptionHandler(value = Exception.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;
    }
    }
  3. 在 application.properties 添加配置
    Spring MVC 开启抛出找不到映射处理的异常,关闭静态资源映射
    1
    2
    spring.mvc.throw-exception-if-no-handler-found=true
    spring.resources.add-mappings=false
  4. 官方示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @ControllerAdvice(basePackageClasses = AcmeController.class)
    public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {

    @ExceptionHandler(YourException.class)
    @ResponseBody
    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)。

  1. 示例:404 静态 HTML 页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    src/
    +- main/
    +- java/
    | + <source code>
    +- resources/
    +- public/
    +- error/
    | +- 404.html
    +- <other public assets>
  2. 示例:系列掩码命名模板文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    src/
    +- main/
    +- java/
    | + <source code>
    +- resources/
    +- templates/
    +- error/
    | +- 5xx.ftl
    +- <other templates>
  3. 对于列复杂的映射,还可以添加实现 ErrorViewResolver 接口的 Bean
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyErrorViewResolver implements ErrorViewResolver {

    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request,
    HttpStatus status, Map<String, Object> model) {
    // Use the request or status to optionally return a ModelAndView
    return ...
    }
    }

相关参考

  1. SpringBoot拦截全局异常并发送邮件给指定邮箱
  2. 零代码如何打造自己的实时监控预警系统
  3. 从零开始搭建ELK+GPE监控预警系统
  4. Java 小应用日志级别异常处理最佳实践
  5. 小应用日志和异常信息聚合开源方案 stackify-log-log4j2
  6. SpringBoot全局异常与数据校验
  7. Spring Learn Doc

Spring Boot 2系列(三十八):全局异常统一处理

http://blog.gxitsky.com/2019/01/30/SpringBoot-38-global-exception-handle/

作者

光星

发布于

2019-01-30

更新于

2022-06-20

许可协议

评论