【问题标题】:Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers设置多个@ControllerAdvice @ExceptionHandlers 的优先级
【发布时间】:2013-10-30 04:08:56
【问题描述】:

我有多个用@ControllerAdvice 注释的类,每个类都有一个@ExceptionHandler 方法。

一个处理 Exception 的目的是如果没有找到更多特定的处理程序,则应该使用它。

可悲的是,Spring MVC 似乎总是使用最通用的情况(Exception)而不是更具体的情况(例如IOException)。

这是人们期望 Spring MVC 的行为方式吗?我正在尝试模拟来自 Jersey 的模式,该模式评估每个 ExceptionMapper(等效组件)以确定它处理的声明类型与已引发的异常之间的距离,并且始终使用最近的祖先。

【问题讨论】:

    标签: spring-mvc exception


    【解决方案1】:

    这是人们期望 Spring MVC 的行为方式吗?

    从 Spring 4.3.7 开始,Spring MVC 的行为如下:它使用 HandlerExceptionResolver 实例来处理处理程序方法抛出的异常。

    默认情况下,Web MVC 配置注册一个 HandlerExceptionResolver bean,一个 HandlerExceptionResolverComposite,它

    委托给其他HandlerExceptionResolvers的列表。

    其他解析器是

    1. ExceptionHandlerExceptionResolver
    2. ResponseStatusExceptionResolver
    3. DefaultHandlerExceptionResolver

    按此顺序注册。对于这个问题,我们只关心ExceptionHandlerExceptionResolver

    解决异常的AbstractHandlerMethodExceptionResolver 通过@ExceptionHandler 方法。

    在上下文初始化时,Spring 将为它检测到的每个 @ControllerAdvice 注释类生成一个 ControllerAdviceBeanExceptionHandlerExceptionResolver 将从上下文中检索这些,并使用 AnnotationAwareOrderComparator 对它们进行排序

    OrderComparator的扩展,支持Spring的Ordered 接口以及@Order@Priority 注释,带有 由 Ordered 实例提供的 order 值,静态覆盖 定义的注释值(如果有)。

    然后它会为每个ControllerAdviceBean 实例注册一个ExceptionHandlerMethodResolver(将可用的@ExceptionHandler 方法映射到它们要处理的异常类型)。这些最终以相同的顺序添加到LinkedHashMap(保留迭代顺序)。

    当异常发生时,ExceptionHandlerExceptionResolver 将遍历这些ExceptionHandlerMethodResolver 并使用第一个可以处理异常的。

    所以这里的重点是:如果你有一个@ControllerAdvice 和一个@ExceptionHandler 用于Exception,它在另一个@ControllerAdvice 类之前注册,并且@ExceptionHandler 用于更具体的异常,例如IOException,第一个将被调用。如前所述,您可以通过让您的 @ControllerAdvice 注释类实现 Ordered 或使用 @Order@Priority 注释并为其赋予适当的值来控制注册顺序。

    【讨论】:

    • 此外,如果@ControllerAdvice 中有多个@ExceptionHandler 方法,则选择处理所引发异常的最具体超类的方法。
    • 在 Spring Boot 2.3.3 中,它不需要在子类上使用 @Order 注释来覆盖父控制器通知类中的控制器通知 ExceptionHandler 方法
    【解决方案2】:

    Sotirios Delimanolis 在他的回答中非常有帮助,经过进一步调查,我们发现,无论如何,在 Spring 3.2.4 中,查找 @ControllerAdvice 注释的代码还会检查 @Order 注释的存在并对 ControllerAdviceBeans 列表进行排序。

    没有@Order 注释的所有控制器的默认顺序是Ordered#LOWEST_PRECEDENCE,这意味着如果您有一个控制器需要具有最低优先级,那么您的所有控制器都需要具有更高的顺序。

    这是一个示例,展示了如何拥有两个带有 ControllerAdvice 和 Order 注释的异常处理程序类,它们可以在发生 UserProfileException 或 RuntimeException 时提供适当的响应。

    class UserProfileException extends RuntimeException {
    }
    
    @ControllerAdvice
    @Order(Ordered.HIGHEST_PRECEDENCE)
    class UserProfileExceptionHandler {
        @ExceptionHandler(UserProfileException)
        @ResponseBody
        ResponseEntity<ErrorResponse> handleUserProfileException() {
            ....
        }
    }
    
    @ControllerAdvice
    @Order(Ordered.LOWEST_PRECEDENCE)
    class DefaultExceptionHandler {
    
        @ExceptionHandler(RuntimeException)
        @ResponseBody
        ResponseEntity<ErrorResponse> handleRuntimeException() {
            ....
        }
    }
    
    • 参见 ControllerAdviceBean#initOrderFromBeanType()
    • 参见 ControllerAdviceBean#findAnnotatedBeans()
    • 参见 ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache()

    享受吧!

    【讨论】:

      【解决方案3】:

      可以使用@Order 注解更改异常处理程序的顺序。

      例如:

      import org.springframework.core.Ordered;
      import org.springframework.core.annotation.Order;
      import org.springframework.web.bind.annotation.ControllerAdvice;
      
      @ControllerAdvice
      @Order(Ordered.HIGHEST_PRECEDENCE)
      public class CustomExceptionHandler {
      
          //...
      
      }
      

      @Order 的值可以是任意整数。

      【讨论】:

        【解决方案4】:

        我还在文档中发现:

        https://docs.spring.io/spring-framework/docs/4.3.4.RELEASE/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html#getExceptionHandlerMethod-org.springframework.web.method.HandlerMethod-java.lang.Exception-

        异常处理方法

        受保护的 ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, 异常异常)

        找到给定的@ExceptionHandler 方法 例外。 默认实现在类中搜索方法 首先是控制器的层次结构,如果没有找到,它会继续 搜索其他 @ExceptionHandler 方法假设一些 @ControllerAdvice Spring 管理的 bean 被检测到。参数: handlerMethod - 引发异常的方法(可能是 null) 异常 - 引发的异常 返回: 处理异常的方法 异常,或为空

        所以这意味着如果你想解决这个问题,你需要在抛出这些异常的控制器中添加你的特定异常处理程序。并且定义一个且唯一一个处理全局默认异常处理程序的 ControllerAdvice。

        这简化了流程,我们不需要 Order 注解来处理问题。

        【讨论】:

          【解决方案5】:

          在 Spring 博客上标题为 Global Exception Handling 的部分中的出色“Exception Handling in Spring MVC”帖子中也有类似的情况。他们的场景涉及检查异常类上注册的 ResponseStatus 注释,如果存在,则重新抛出异常以让框架处理它们。您也许可以使用这种通用策略 - 尝试确定是否有更合适的处理程序并重新抛出。

          或者,您可以查看其他一些异常处理策略。

          【讨论】:

            【解决方案6】:

            要处理的重要类:

            **@Order(Ordered.HIGHEST_PRECEDENCE)**
            public class FunctionalResponseEntityExceptionHandler {
                private final Logger logger = LoggerFactory.getLogger(FunctionalResponseEntityExceptionHandler.class);
            
                @ExceptionHandler(EntityNotFoundException.class)
                public final ResponseEntity<Object> handleFunctionalExceptions(EntityNotFoundException ex, WebRequest request)
                {
                    logger.error(ex.getMessage() + " " + ex);
                    ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.getMessage(),
                            request.getDescription(false),HttpStatus.NOT_FOUND.toString());
                    return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND);
                }
            }
            

            其他低优先级的异常

            @ControllerAdvice
                public class GlobalResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
                {
                private final Logger logger = LoggerFactory.getLogger(GlobalResponseEntityExceptionHandler.class);
                @ExceptionHandler(Exception.class)
                public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request)
                {
                    logger.error(ex.getMessage()+ " " + ex);
                    ExceptionResponse exceptionResponse= new ExceptionResponse(new Date(), ex.toString(),
                            request.getDescription(false),HttpStatus.INTERNAL_SERVER_ERROR.toString());
                }
                }
            

            【讨论】:

              【解决方案7】:

              您也可以使用数字值,如下所示

              @Order(value = 100)
              

              较低的值具有较高的优先级。默认值为 * {@code Ordered.LOWEST_PRECEDENCE},表示最低优先级(输给任何 其他 * 指定订单值)

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-06-26
                • 2014-10-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多