問題描述
問題起源於在公司內部,我們創建了自己的自定義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
來幫他們修復問題