问题起源于在公司内部,我们创建了自己的自定义 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的类为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来帮他们修复问题
有时候在项目里面会有多个 @ControllerAdvice 或者 @RestControllerAdvice 的情况,这时候,可能就会出现一个特定的异常被其他的处理掉,从而出现一些奇怪的问题。
1.先看异常是不是@ExceptionHandler中配置的 2.异常可能提示的是A异常,实际上捕捉的B异常,例如JsonParseExcepetion被捕捉,抛出的是HttpMessageNotReadableException异常 源码解析: AbstractHandlerExceptionResolver中调用resolveException方法 public ModelAndView resolveException(HttpServletRequest request, HttpServl
DispatcherServlet的doDispatch方法最后调用了如下方法。 最后一个参数就是我们执行过程中出现的异常。 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); processDispatchResult方法 private void processDispatchResult(HttpServletRequest re 1、使用aop进行切面拦截异常 2、controller每个方法都用try-catch捕获异常 3、增加一个@RestControllerAdvice标注的类,负责处理我们项目的异常 一般放在一个类中就不会有这种情况了,而我用了两个类全局异常处理类和接口参数校验处理类 还有一种情况是一用力别人的模块,模块中是用了@RestControllerAdvice的类, 多个加了@RestControllerAdvice的类它们会按照类名依次加载,如果前面的类有能处 这个比较简单,继承ErrorController接口实现自己的Controller即可。 可参见:org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController  代码参照[非最终代码,部分命名可能不规范,参考用]: package com.minipro
ControllerAdvice 是一个注解,用于定义一个全局的异常处理类。在 Spring MVC 中,当控制器方法抛出异常时,可以使用 ControllerAdvice 来捕获并处理这些异常。 通过使用 ControllerAdvice 注解,我们可以定义一个带有 @ExceptionHandler 注解的方法,该方法可以处理特定类型的异常。这些异常处理方法可以被应用于整个应用程序中的所有控制器。 ControllerAdvice 还可以用于定义其他类型的通用处理方法,例如处理绑定参数错误或处理全局的数据绑定。 需要注意的是,ControllerAdvice 注解只能用于类上,而不能用于方法上。同时,被 ControllerAdvice 注解的类必须被 Spring 扫描到,通常通过 @ComponentScan 或 @SpringBootApplication 注解来实现扫描。 希望这个回答能够帮到你!如果你还有其他问题,请继续提问。