Spring Boot 全局异常处理策略设计之@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析

 更新时间:2026年01月09日 09:48:02   作者:Qiuner  
SpringBoot全局异常处理策略设计@ExceptionHandler与@ControllerAdvice生效原理源码解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Spring Boot 全局异常处理策略设计(三):@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析

1. 从一个常见疑问说起

很多人在使用全局异常处理时,都会遇到类似问题:

  • 为什么这个异常没进我的 @ControllerAdvice?
  • 多个 @ExceptionHandler 时,哪个先生效?
  • 参数、返回值为什么能自动解析?
  • 为什么同一个异常,在不同 Controller 表现不一样?

这些问题,用“注解怎么写”是回答不了的,只能从源码解释

2. ExceptionHandlerExceptionResolver 是谁在干活

在上一篇中我们已经知道,异常最终会进入这条责任链:

ExceptionHandlerExceptionResolver
→ ResponseStatusExceptionResolver
→ DefaultHandlerExceptionResolver

@ExceptionHandler 和 @ControllerAdvice 的真正执行者,就是:

ExceptionHandlerExceptionResolver

3. ExceptionHandlerExceptionResolver 的核心职责

从类注释就能看出它的定位:

/**
 * An {@link HandlerExceptionResolver} that resolves exceptions through
 * {@link ExceptionHandler} methods.
 */

它只做一件事:

找到能处理当前异常的 @ExceptionHandler 方法,并执行它

4. @ExceptionHandler 方法是如何被扫描的

4.1 初始化阶段:扫描所有异常处理方法

在容器启动阶段,ExceptionHandlerExceptionResolver 会执行初始化逻辑:

public void afterPropertiesSet() {
    initExceptionHandlerAdviceCache();
}

4.2 扫描 @ControllerAdvice

private void initExceptionHandlerAdviceCache() {
    List<ControllerAdviceBean> adviceBeans =
        ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
}

关键点:

  • 扫描整个 Spring 容器
  • 找出所有标注了 @ControllerAdvice 的 Bean
  • 封装为 ControllerAdviceBean

4.3 ControllerAdvice 的“作用范围”不是全局那么简单

@ControllerAdvice 支持条件匹配:

@ControllerAdvice(
    basePackages = "com.example.web",
    annotations = RestController.class
)

源码中通过 HandlerTypePredicate 判断是否适用当前 Controller。

👉 这也是为什么:

有些 Advice 明明存在,却对某些 Controller 不生效

5. @ExceptionHandler 方法是如何被缓存的

5.1 ExceptionHandlerMethodResolver

每一个 Controller 或 Advice,都会对应一个解析器:

new ExceptionHandlerMethodResolver(beanType);

它会:

  • 扫描所有方法
  • 找出 @ExceptionHandler
  • 建立异常类型 → 方法的映射关系

5.2 一个方法可以处理多个异常

@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
public ErrorResponse handle(Exception e) {}

源码中会把它拆解成多条映射关系。

5.3 异常匹配是“最近优先”

如果存在继承关系:

RuntimeException
└── IllegalArgumentException

IllegalArgumentException 会优先匹配,而不是父类异常。

这是通过 ExceptionDepthComparator 实现的。

6. 异常发生时,Resolver 是如何找方法的

异常真正发生后,会进入:

protected ModelAndView doResolveHandlerMethodException(...)

核心逻辑:

  1. 先找 Controller 内部的 @ExceptionHandler
  2. 再找全局 @ControllerAdvice
  3. 找到就执行,找不到返回 null

6.1 Controller 内部优先于 ControllerAdvice

这是一个非常重要的优先级规则:

局部异常处理 > 全局异常处理

源码中体现为:

getExceptionHandlerMethod(handlerMethod, exception)

先基于当前 Controller 查找。

7. @ExceptionHandler 方法是如何被执行的

一旦找到目标方法,Spring 会把它包装成:

ServletInvocableHandlerMethod

这个类你在 MVC 参数解析中已经见过。

7.1 参数是如何自动注入的

@ExceptionHandler(Exception.class)
public ErrorResponse handle(
    Exception ex,
    HttpServletRequest request
) {}

参数解析复用的正是:

  • HandlerMethodArgumentResolver 体系

👉 异常处理方法,本质上也是一个 MVC 方法。

7.2 返回值是如何写入响应的

返回值处理同样复用:

  • HandlerMethodReturnValueHandler
  • HttpMessageConverter

所以你可以:

  • 返回对象
  • 返回 ResponseEntity
  • 返回 void

8. 为什么 @ResponseBody 能生效

在 Spring Boot 中,常见写法是:

@RestControllerAdvice

它本质等价于:

@ControllerAdvice
@ResponseBody

@ResponseBody 的解析发生在:

  • 返回值处理阶段
  • 由 RequestResponseBodyMethodProcessor 完成

9. 多个 @ControllerAdvice 的执行顺序

9.1 顺序规则

优先级由以下规则决定:

  1. @Order
  2. Ordered 接口
  3. 默认顺序(最低优先级)
@Order(1)
@RestControllerAdvice
class BizExceptionAdvice {}
@Order(2)
@RestControllerAdvice
class SystemExceptionAdvice {}

9.2 为什么顺序很重要

因为:

  • 第一个匹配成功的异常处理方法会直接返回
  • 后续 Advice 不再执行

10. 常见“异常不生效”的根本原因

现象根本原因
Advice 不生效basePackages 不匹配
方法不进异常类型不匹配
被吞掉前面 Resolver 已处理
返回 500Resolver 返回 null

这些问题,只有看源码才能彻底理解

11. 异常处理方法执行流程图

图1 @ExceptionHandler 方法解析与执行流程

12. 本篇关键认知升级

到这里,你应该已经清楚:

  1. @ExceptionHandler 并不是“魔法”
  2. ControllerAdvice 不是“全局兜底”,而是有严格匹配规则
  3. 异常处理方法本质上是一个 MVC Handler
  4. Resolver 链决定了异常的最终命运

13. 下一篇预告

到目前为止,我们讲的还是 Spring MVC 层面的异常
但在 Spring Boot 中,还有一个绕不开的存在:

/error

  • 它什么时候被触发?
  • 为什么有的异常进了 /error,而不是 ControllerAdvice?
  • ErrorController、ErrorAttributes 是干什么的?

👉 下一篇,我们正式进入 Spring Boot 的异常“二次封装世界”。

参考资料

  • Spring Framework Reference – Exception Handling
    https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-exceptionhandler.html
  • ExceptionHandlerExceptionResolver 源码
    https://github.com/spring-projects/spring-framework

到此这篇关于Spring Boot 全局异常处理策略设计之@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析的文章就介绍到这了,更多相关Spring Boot 全局异常内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MyBatis之动态SQL和缓存使用及说明

    MyBatis之动态SQL和缓存使用及说明

    这篇文章主要介绍了MyBatis之动态SQL和缓存使用及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2026-05-05
  • 解决idea打包成功但是resource下的文件没有成功的问题

    解决idea打包成功但是resource下的文件没有成功的问题

    这篇文章主要介绍了解决idea打包成功但是resource下的文件没有成功的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 详解Java关于JDK中时间日期的API

    详解Java关于JDK中时间日期的API

    这篇文章主要介绍了详解Java关于JDK中时间日期的API,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • SpringBoot中@ComponentScan注解过滤排除不加载某个类的3种方法

    SpringBoot中@ComponentScan注解过滤排除不加载某个类的3种方法

    这篇文章主要给大家介绍了关于SpringBoot中@ComponentScan注解过滤排除不加载某个类的3种方法,文中通过实例代码介绍的非常详细,对大家学习或者使用SpringBoot具有一定的参考学习价值,需要的朋友可以参考下
    2023-07-07
  • 基于hashmap 的扩容和树形化全面分析

    基于hashmap 的扩容和树形化全面分析

    这篇文章主要介绍了hashmap 的扩容和树形化的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • LogBack+MybatisPlus实现打印sql日志

    LogBack+MybatisPlus实现打印sql日志

    本文主要介绍了LogBack+MybatisPlus实现打印sql日志,设置异步appender实现按天滚动切割日志,保留3天历史并自动清理,替代P6Spy避免日志过大,同时需配置MybatisPlus日志输出路径,并区分MyBatis与MyBatisPlus日志输出
    2025-09-09
  • 教你怎么用Java数组和链表实现栈

    教你怎么用Java数组和链表实现栈

    本篇文章为大家详细介绍了怎么用Java数组和链表实现栈,文中有非常详细的代码示例及注释,对正在学习java的小伙伴们很有帮助,需要的朋友可以参考下
    2021-05-05
  • 详解5种Java中常见限流算法

    详解5种Java中常见限流算法

    在高并发系统中,出于系统保护角度考虑,通常会对流量进行限流;不但在工作中要频繁使用,而且也是面试中的高频考点。本文就为大家整理了5种Java中常见限流算法,需要的可以参考一下
    2023-04-04
  • java的NIO管道用法代码分享

    java的NIO管道用法代码分享

    这篇文章主要介绍了java的NIO管道用法代码分享,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Springboot发送post请求的几种方式总结

    Springboot发送post请求的几种方式总结

    这篇文章主要为大家详细介绍了Springboot发送post请求的几种方式,文中的示例代码讲解详细,对我们学习或工作有一定的帮助,感兴趣的小伙伴可以了解一下
    2024-01-01

最新评论