问题描述
问题起源于在公司内部,我们创建了自己的自定义web
模块的stater
,在其中有统一的异常处理,普通的异常处理我们称之为ExceptionResolver
,还有一种我们称之为FeignExceptionResolver
,专门为了处理FeignException
,为啥会单独写一个处理FeignException
呢,主要是考虑到可能有的模块会没有引入Feign
的包,从而造成启动报错。而这个在近期又出现了另一个问题,就是当别的一些同学乱来,在自己的应用上乱指定scanBasePackages
导致这两个Resolver
的加载顺序被打乱了,从而导致FeignException
被普通异常中的搂底操作所处理,造成提示错误。当环境中有多个@ControllerAdvice
或者@RestControllerAdvice
注解标注的类,它们是有优先级顺序的,排在前面的先执行。
问题处理
描述清楚了问题,那就想着怎么去处理它,既然说是加载顺序造成的错误,那么我们应该想到那就需要去调整其加载顺序,而此时自然而然会想到@Order
,由于我们使用的自定义starter
,所以这些类均使用@Bean
在spring.factories
文件指向的类中去完成被Spring
的管理,所以我们做了以下尝试:
- 在
@Bean
处使用@Order
,结果发现无效 - 让标注
@ControllerAdvice
或者@RestControllerAdvice
的类实现Ordered
接口,无效 - 在标注
@ControllerAdvice
或者@RestControllerAdvice
的类上标注@Order
,成功解决问题
问题分析
按理说,一般情况下@Order
和实现Ordered
接口的效果应该是一样的,那么这里究竟是哪里出了问题呢,我们从源码入手。
一脚入坑
首先我们通过查找@ControllerAdvice
在哪些地方被用到,很容易观察到其应该被ExceptionHandlerExceptionResolver
所处理,再观察其变量,我们看到有这样一段代码
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
new LinkedHashMap<>();
这个数据结构也表明了,多个同时存在时应该是有优先级顺序的。
接下来我们查找这个变量被设置值的方法:
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Looking for exception mappings: " + getApplicationContext());
}
// 2. put 前的参数来源
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 3. 对多个 ControllerAdviceBean 进行排序
AnnotationAwareOrderComparator.sort(adviceBeans);
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
if (resolver.hasExceptionMappings()) {
// 1. 变量被设置值
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
if (logger.isInfoEnabled()) {
logger.info("Detected @ExceptionHandler methods in " + adviceBean);
}
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
}
}
}
}
我们看到Map
被设置值的地方为1
处,而它的参数来源于2
处,在2
处查找了所有标注@ControllerAdvice
或者@RestControllerAdvice
的类为ControllerAdviceBean
的List
集合,后在3
对其进行排序。
在找到这部分的源码,emmmm
一脚就踩坑里了,下意识以为直接看3
中的实现,即可找到原因,好了,就默默追踪3
吧,其调用方法为AnnotationAwareOrderComparator.sort(adviceBeans);
public static void sort(List<?> list) {
if (list.size() > 1) {
list.sort(INSTANCE);
}
}
关键在于INSTANCE
即public static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();
那么具体其实还得看父类中compare
方法的实现,这里既然能丢给list.sort
方法,那么必然实现了Comparator
接口中的compare
方法,追踪发现这里又调用了父类OrderComparator
中的doCompare
方法
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered);
boolean p2 = (o2 instanceof PriorityOrdered);
if (p1 && !p2) {
return -1;
}
else if (p2 && !p1) {
return 1;
}
int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider);
return Integer.compare(i1, i2);
}
通过这里的代码我们可以看到其实现应该在getOrder
方法中,那么再次往下看
private int getOrder(@Nullable Object obj, @Nullable OrderSourceProvider sourceProvider) {
Integer order = null;
if (obj != null && sourceProvider != null) {
Object orderSource = sourceProvider.getOrderSource(obj);
if (orderSource != null) {
if (orderSource.getClass().isArray()) {
Object[] sources = ObjectUtils.toObjectArray(orderSource);
for (Object source : sources) {
order = findOrder(source);
if (order != null) {
break;
}
}
}
else {
order = findOrder(orderSource);
}
}
}
return (order != null ? order : getOrder(obj));
}
这里可以看到,真正的实现又在findOrder
方法中实现,不得不说,Spring
的源码永远是如此绕,啊啊啊啊啊,蛋疼,继续往下点吧这里会先调用子类AnnotationAwareOrderComparator
的findOrder
方法
protected Integer findOrder(Object obj) {
// Check for regular Ordered interface
Integer order = super.findOrder(obj);
if (order != null) {
return order;
}
// Check for @Order and @Priority on various kinds of elements
if (obj instanceof Class) {
return OrderUtils.getOrder((Class<?>) obj);
}
else if (obj instanceof Method) {
Order ann = AnnotationUtils.findAnnotation((Method) obj, Order.class);
if (ann != null) {
return ann.value();
}
}
else if (obj instanceof AnnotatedElement) {
Order ann = AnnotationUtils.getAnnotation((AnnotatedElement) obj, Order.class);
if (ann != null) {
return ann.value();
}
}
else {
order = OrderUtils.getOrder(obj.getClass());
if (order == null && obj instanceof DecoratingProxy) {
order = OrderUtils.getOrder(((DecoratingProxy) obj).getDecoratedClass());
}
}
return order;
}
这里就会发现它第一句注释,其实就是检查了是否实现Ordered
接口,我们点过去瞅瞅
protected Integer findOrder(Object obj) {
return (obj instanceof Ordered ? ((Ordered) obj).getOrder() : null);
}
看到这里,心里就开始心生疑惑,咦,这不就是如果这个类实现了Ordered
接口重写了getOrder
方法,这里就应该会被调用,完成排序啊,那为啥不生效,当时心里是懵逼的,这咋回事!!!
重头来过
在懵逼了一会之后,想想还是得回到ExceptionHandlerExceptionResolver
的initExceptionHandlerAdviceCache
方法重新开始。
private void initExceptionHandlerAdviceCache() {
.
.
.
// 2. put 前的参数来源
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
// 3. 对多个 ControllerAdviceBean 进行排序
AnnotationAwareOrderComparator.sort(adviceBeans);
.
.
.
}
再回到这里,对着这两行代码发了会呆,我终于治好了我的瞎,他排序的是ControllerAdviceBean
那么它其中是不是做了一些处理,这时候我们点开ControllerAdviceBean
类康康
public class ControllerAdviceBean implements Ordered {
.
.
.
private final int order;
/**
* Returns the order value extracted from the {@link ControllerAdvice}
* annotation, or {@link Ordered#LOWEST_PRECEDENCE} otherwise.
*/
@Override
public int getOrder() {
return this.order;
}
private static int initOrderFromBean(Object bean) {
return (bean instanceof Ordered ? ((Ordered) bean).getOrder() : initOrderFromBeanType(bean.getClass()));
}
private static int initOrderFromBeanType(@Nullable Class<?> beanType) {
Integer order = null;
if (beanType != null) {
order = OrderUtils.getOrder(beanType);
}
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
}
.
.
.
}
去掉无用内容我们看到了这些,它实现了Ordered
接口,且有个order
字段,而重写的getOrder
方法返回为当前这个order
字段的值,而上面踩坑部分,也不能说一定用没有,至少连贯起来能知道最终排序其实就是排序多个@ControllerAdvice
或者@RestControllerAdvice
标注的类生成的ControllerAdviceBean
中order
字段的值,而它的来源又是什么呢,就是上面initOrderFromBeanType
方法中OrderUtils.getOrder(beanType)
,好了,感觉快见到真相了。
柳暗花明
打开OrderUtils#getOrder
方法
public static Integer getOrder(Class<?> type) {
Object cached = orderCache.get(type);
if (cached != null) {
return (cached instanceof Integer ? (Integer) cached : null);
}
Order order = AnnotationUtils.findAnnotation(type, Order.class);
Integer result;
if (order != null) {
result = order.value();
}
else {
result = getPriority(type);
}
orderCache.put(type, (result != null ? result : NOT_ANNOTATED));
return result;
}
emmmm
这里就发现极其简单了,就找了这个标注@ControllerAdvice
或者@RestControllerAdvice
上有没有标注@Order
注解,或者是@Priority
注解,并没有检查有没有实现Ordered
接口,故而无效,单单只检查了类上有无这俩注解。看到这里也总算是搞明白了这个Bug
。
结语
- 修复
Bug
的同时如果有时间还是得明白其中发生了些啥 - 如果有规范就按规范来吧,不要瞎胡来,此
Bug
就是由于同学瞎胡来,导致我们得改通用的starter
来帮他们修复问题
网友评论