springmvc 多个ControllerAdvice执行的优先级问题分析
1. 背景
在一个工程中,基础框架已经定义了一个全局的异常拦截器了,然后在自己的工程中又定义了一个拦截器,
省略了部分之后如下:
1 |
|
1 |
|
第一个@RestControllerAdvice
类有一个保底的拦截@ExceptionHandler(Throwable.class)
,第二个是SpringSecurity中的认证异常的拦截@ExceptionHandle(InsufficientAuthenticationException.class)
工程中出现了InsufficientAuthenticationException
异常,本来按照HandlerException的顺序应该是优先执行异常最接近的@ExceptionHandle(InsufficientAuthenticationException.class)
,但是实际却执行了@ExceptionHandler(Throwable.class)
2. 解决
在有多个 @ControllerAdvice
的情况下,需要给每个@ControllerAdvice
对应的类添加顺序,来保证执行的先后顺序
如上面的两个类,添加@Order注解或者实现Ordered接口,数字越小,优先级越高
1 |
|
1 |
|
这样添加了@Order(100)
和@Order(10000)
之后,SecurityHandlerAdvice就会优先执行到
3. 源码分析
当发生异常时,SpringMVC会调起HandlerExceptionResolver#resolveException()
来处理异常,这个接口也只有这一个方法,默认的已经加载好了ExceptionHandlerExceptionResolver
这个解析器。
ExceptionHandlerExceptionResolver
先调用父方法doResolveHandlerMethodException()
- 然后再执行
doResolveHandlerMethodException()
- 然后再执行
- 调起
getExceptionHandlerMethod()
,然后内部遍历this.exceptionHandlerAdviceCache()
这个LinkedHashMap来获取@ControllerAdvice
对应的类
- 调起
- 分析类中的方法使用
resolver.resolveMethod(exception)
会根据异常优先选取最接近的异常(及有本异常则匹配,没有则匹配父异常,一直到Throable异常)
- 分析类中的方法使用
调试的栈如下
在第3步的时候就会选取一个@ControllerAdvice
对应的类了,而这又是HashMap的遍历,因此需要继续寻找初始化的时候是怎么put进HashMap
的。
这里分析采用倒序的方式,更容易理解
exceptionHandlerAdviceCache
是初始化的时候就配置好了,因此我们要重启调试,到exceptionHandlerAdviceCache
初始化的时候断点
在ExceptionHandlerExceptionResolver
中,只有initExceptionHandlerAdviceCache()
方法会设置this.exceptionHandlerAdviceCache
断点定位到put的地方
可以看出来1处就是从Application中拿到ControllerAdviceBean
,而ControllerAdviceBean就是封装了ControllerAdvice
类的
进入ControllerAdviceBean.findAnnotatedBeans(getApplicationContext())
方法
- 可以看出1的位置就是找到
ControllerAdvice
注解,
- 可以看出1的位置就是找到
- 在2的位置封装并添加到adviceBeans中
- 在3的位置进行重排序
OrderComparator.sort(adviceBeans)
- 在3的位置进行重排序
OrderComparator.sort()
是Spring中很重要的一个工具类,它主要用于对容器中一个接口多个实现的List进行排序,排序的规则是按照有@Order
注解或者是实现Ordered
接口,并且以数字越小越靠前。
Spring中实现了@Oreder
和Ordered
的都是通过这个注解排序的。
回到本文中,因此就明确了要有先后顺序必须要自己定义Bean的顺序。
其实大部分Spring中的注入Bean顺序的先后都是此思路。