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顺序的先后都是此思路。
