设置多个@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会注册一个HandlerExceptionResolver
, HandlerExceptionResolverComposite
委托给其他
HandlerExceptionResolvers
列表。
那些其他parsing器是
-
ExceptionHandlerExceptionResolver
-
ResponseStatusExceptionResolver
-
DefaultHandlerExceptionResolver
按此顺序注册。 对于这个问题我们只关心ExceptionHandlerExceptionResolver
。
一个
AbstractHandlerMethodExceptionResolver
,通过@ExceptionHandler
方法解决exception。
在上下文初始化时,Spring将为其检测到的每个@ControllerAdvice
注释类生成一个ControllerAdviceBean
。 ExceptionHandlerExceptionResolver
将从上下文中检索这些内容,并使用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注解来解决问题。