设置多个@ControllerAdvice @ExceptionHandlers的优先级

我有@ControllerAdvice批注了多个类,每个都有一个@ExceptionHandler方法。

一个处理Exception的意图是,如果没有find更具体的处理程序,应该使用这个。

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

这是如何期望Spring MVC的行为? 我试图从泽西模拟一个模式,它评估每个ExceptionMapper (等价的组件),以确定它处理的声明types距离抛出的exception有多远,并始终使用最近的祖先。

这是如何期望Spring MVC的行为?

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

默认情况下,Web MVCconfiguration会注册一个HandlerExceptionResolverHandlerExceptionResolverComposite

委托给其他HandlerExceptionResolvers列表。

那些其他parsing器是

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

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

一个AbstractHandlerMethodExceptionResolver ,通过@ExceptionHandler方法解决exception。

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

OrderComparator一个扩展,它支持Spring的Ordered接口以及@Order@Priority注解,由Ordered实例提供的一个订单值覆盖一个静态定义的注解值(如果有的话)。

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

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

所以这里的重点是:如果你有一个@ControllerAdvice Exception在另一个@ControllerAdvice类之前被注册了,而@ExceptionHandler是一个更具体的exception,比如IOException ,那么第一个会被调用。 如前所述,您可以通过让您的@ControllerAdvice注释类实现Ordered来控制注册顺序,或者使用@Order@Priority对其进行注释并给它一个适当的值。

Sotirios Delimanolis对他的回答非常有帮助,在进一步的调查中,我们发现在3.2.4春季,寻找@ControllerAdvice注释的代码也检查@Order注释的存在,并对ControllerAdviceBean列表进行sorting。

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

下面是一个示例,展示如何使用ControllerAdvice和Order注释来创build两个exception处理程序类,以便在出现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()

请享用!

exception处理程序的顺序可以使用@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的值可以是任何整数。

在Spring博客的“ Spring MVCexception处理 ”一文中,“ Global Exception Handling ”一节中也有类似的情况。 他们的场景包括检查在exception类上注册的ResponseStatus注解,如果存在的话,重新抛出exception让框架处理它们。 你也许能够使用这个总体策略 – 试着确定是否有一个更合适的处理程序,并重新投掷。

另外,还有一些其他的exception处理策略可以用来代替。

我还在文档中发现:

ExceptionHandlerMethod

受保护的ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod,exceptionexception)

查找给定exception的@ExceptionHandler方法。 默认实现首先在控制器的类层次结构中search方法,如果未find,则继续search额外的@ExceptionHandler方法,假定检测到一些@ControllerAdvice Springpipe理的bean 。 参数:handlerMethod – 引发exception的方法(可以为null)exception – 引发的exception返回:处理exception的方法,或null

所以这意味着如果你想解决这个问题,你将需要在控制器中添加你的特定的exception处理程序,抛出这些exception。 并定义一个且唯一的ControllerAdvice来处理Global缺省exception处理程序。

这简化了stream程,我们不需要Order注解来解决问题。