3

引言

在开发基于Spring Boot的应用时,异常是常见的。优雅地处理异常不仅可以提升应用的健壮性,还能让开发人员快速定位到BUG。虽然SpringBoot框架有部分的异常处理了,但是对于我们开发者而言BUG提示信息太广,定位不准,因此我们需要对这些异常进行统一的捕获并处理。

未使用全局异常:我们只能看出是服务器的问题。
image.png
使用全局异常:可以看出改错误是在创建培训资源时发生的错误。
image.png

一. 注解介绍

Spring Boot提供了多种方式来处理全局异常,其中最常见和推荐的方式是使用@ControllerAdvice或@RestControllerAdvice注解。(注:@RestControllerAdvice是一个组合注解,由@ControllerAdvice、@ResponseBody组成)
官方文档:

Controller Advice
Typically, the @ExceptionHandler, @InitBinder, and @ModelAttribute methods apply within the @Controller class (or class hierarchy) in which they are declared. If you want such methods to apply more globally (across controllers), you can declare them in a class annotated with @ControllerAdvice or @RestControllerAdvice.
@ControllerAdvice is annotated with @Component, which means that such classes can be registered as Spring beans through component scanning . @RestControllerAdvice is a composed annotation that is annotated with both @ControllerAdvice and @ResponseBody, which essentially means @ExceptionHandler methods are rendered to the response body through message conversion (versus view resolution or template rendering).

翻译:

通常,@ExceptionHandler、@InitBinder 和 @ModelAttribute 方法应用于它们被声明的 @Controller 类(或类层次结构)中。如果你想让这些方法更全局地应用(跨控制器),你可以将它们声明在一个用 @ControllerAdvice 或 @RestControllerAdvice 注解的类中。
@ControllerAdvice 被 @Component 注解,这意味着这样的类可以通过组件扫描注册为 Spring Bean。@RestControllerAdvice 是一个组合注解,它同时被 @ControllerAdvice 和 @ResponseBody 注解,这基本上意味着 @ExceptionHandler 方法通过消息转换(而不是视图解析或模板渲染)将结果渲染到响应体中。
image.png
image.png

二. Spring Boot中的全局异常处理

1.创建一个自定义的异常类(这一步是可选的,但推荐用于区分不同类型的错误,细化BUG)

public static class ErrorMessage {
        private String message;

        private int status;

        private List<String> errors = new ArrayList<>();

        @JsonIgnore
        private final HttpStatus httpStatus;

        @JsonIgnore
        private final GetErrors getErrorsFn; 
        // get set 及构造函数省略 
}

2.创建一个使用@RestControllerAdvice注解的类,用于全局异常处理

  
@RestControllerAdvice
public class GlobalExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private final HashMap<Class<? extends Exception>, ErrorMessage> exceptionStatusMap = new HashMap<Class<? extends Exception>, ErrorMessage>();

    public GlobalExceptionHandler() {
        // 加入自定义的异常
        this.exceptionStatusMap.put(Exception.class, new ErrorMessage("exception", HttpStatus.SERVICE_UNAVAILABLE, 50000));
        this.exceptionStatusMap.put(RuntimeException.class, new ErrorMessage("runtime exception", HttpStatus.SERVICE_UNAVAILABLE, 50001));
    }

    /**
     * runtime异常.
     *
     * @param request   请求
     * @param exception 异常
     * @return 异常处理器
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity<ErrorMessage> runtimeExceptionHandler(HttpServletRequest request, Exception exception) {
        if (exception.getClass().equals(RuntimeException.class) || exception.getClass().equals(Exception.class)) {
            logger.error("程序运行异常: 主机 {} 调用地址 {} 错误信息 {}",
                request.getRemoteHost(), request.getRequestURL(), exception.getMessage());
            exception.printStackTrace();
        }

        ErrorMessage errorMessage;
        if (!this.exceptionStatusMap.containsKey(exception.getClass())) {
            logger.warn("未找到" + exception.getClass() + "的异常处理器,请添加");
            if (exception instanceof RuntimeException) {
                errorMessage = this.exceptionStatusMap.get(RuntimeException.class);
            } else {
                errorMessage = this.exceptionStatusMap.get(Exception.class);
            }
            this.logger.warn("异常信息", errorMessage.getMessage());
            exception.printStackTrace();
        } else {
            errorMessage = this.exceptionStatusMap.get(exception.getClass());
        }

        return new ResponseEntity<>(errorMessage.addException(exception), errorMessage.httpStatus);
    }
}

三.注解@ControllerAdvice的工作原理

Spring MVC : 注解@ControllerAdvice的工作原理

参考文章
spring 官网


吴季分
390 声望13 粉丝