調整多個ControllerAdvice的執行順序

問題描述

問題起源於在公司內部,我們創建了自己的自定義web模塊的stater,在其中有統一的異常處理,普通的異常處理我們稱之爲ExceptionResolver,還有一種我們稱之爲FeignExceptionResolver,專門爲了處理FeignException,爲啥會單獨寫一個處理FeignException呢,主要是考慮到可能有的模塊會沒有引入Feign的包,從而造成啓動報錯。而這個在近期又出現了另一個問題,就是當別的一些同學亂來,在自己的應用上亂指定scanBasePackages導致這兩個Resolver的加載順序被打亂了,從而導致FeignException被普通異常中的摟底操作所處理,造成提示錯誤。當環境中有多個@ControllerAdvice或者@RestControllerAdvice註解標註的類,它們是有優先級順序的,排在前面的先執行

問題處理

描述清楚了問題,那就想着怎麼去處理它,既然說是加載順序造成的錯誤,那麼我們應該想到那就需要去調整其加載順序,而此時自然而然會想到@Order,由於我們使用的自定義starter,所以這些類均使用@Beanspring.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的類爲ControllerAdviceBeanList集合,後在3對其進行排序。

在找到這部分的源碼,emmmm一腳就踩坑裏了,下意識以爲直接看3中的實現,即可找到原因,好了,就默默追蹤3吧,其調用方法爲AnnotationAwareOrderComparator.sort(adviceBeans);

public static void sort(List<?> list) {
		if (list.size() > 1) {
			list.sort(INSTANCE);
		}
}

關鍵在於INSTANCEpublic 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的源碼永遠是如此繞,啊啊啊啊啊,蛋疼,繼續往下點吧這裏會先調用子類AnnotationAwareOrderComparatorfindOrder方法

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方法,這裏就應該會被調用,完成排序啊,那爲啥不生效,當時心裏是懵逼的,這咋回事!!!

重頭來過

在懵逼了一會之後,想想還是得回到ExceptionHandlerExceptionResolverinitExceptionHandlerAdviceCache方法重新開始。

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標註的類生成的ControllerAdviceBeanorder字段的值,而它的來源又是什麼呢,就是上面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來幫他們修復問題
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章