文章目录
- [Spring Boot 全局异常处理策略设计(三):@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析](#Spring Boot 全局异常处理策略设计(三):@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析)
-
- [1. 从一个常见疑问说起](#1. 从一个常见疑问说起)
- [2. ExceptionHandlerExceptionResolver 是谁在干活](#2. ExceptionHandlerExceptionResolver 是谁在干活)
- [3. ExceptionHandlerExceptionResolver 的核心职责](#3. ExceptionHandlerExceptionResolver 的核心职责)
- [4. @ExceptionHandler 方法是如何被扫描的](#4. @ExceptionHandler 方法是如何被扫描的)
-
- [4.1 初始化阶段:扫描所有异常处理方法](#4.1 初始化阶段:扫描所有异常处理方法)
- [4.2 扫描 @ControllerAdvice](#4.2 扫描 @ControllerAdvice)
- [4.3 ControllerAdvice 的"作用范围"不是全局那么简单](#4.3 ControllerAdvice 的“作用范围”不是全局那么简单)
- [5. @ExceptionHandler 方法是如何被缓存的](#5. @ExceptionHandler 方法是如何被缓存的)
-
- [5.1 ExceptionHandlerMethodResolver](#5.1 ExceptionHandlerMethodResolver)
- [5.2 一个方法可以处理多个异常](#5.2 一个方法可以处理多个异常)
- [5.3 异常匹配是"最近优先"](#5.3 异常匹配是“最近优先”)
- [6. 异常发生时,Resolver 是如何找方法的](#6. 异常发生时,Resolver 是如何找方法的)
-
- [6.1 Controller 内部优先于 ControllerAdvice](#6.1 Controller 内部优先于 ControllerAdvice)
- [7. @ExceptionHandler 方法是如何被执行的](#7. @ExceptionHandler 方法是如何被执行的)
-
- [7.1 参数是如何自动注入的](#7.1 参数是如何自动注入的)
- [7.2 返回值是如何写入响应的](#7.2 返回值是如何写入响应的)
- [8. 为什么 @ResponseBody 能生效](#8. 为什么 @ResponseBody 能生效)
- [9. 多个 @ControllerAdvice 的执行顺序](#9. 多个 @ControllerAdvice 的执行顺序)
-
- [9.1 顺序规则](#9.1 顺序规则)
- [9.2 为什么顺序很重要](#9.2 为什么顺序很重要)
- [10. 常见"异常不生效"的根本原因](#10. 常见“异常不生效”的根本原因)
- [11. 异常处理方法执行流程图](#11. 异常处理方法执行流程图)
- [12. 本篇关键认知升级](#12. 本篇关键认知升级)
- [13. 下一篇预告](#13. 下一篇预告)
- 参考资料
- 结束语

Spring Boot 全局异常处理策略设计(三):@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析
1. 从一个常见疑问说起
很多人在使用全局异常处理时,都会遇到类似问题:
- 为什么这个异常没进我的 @ControllerAdvice?
- 多个 @ExceptionHandler 时,哪个先生效?
- 参数、返回值为什么能自动解析?
- 为什么同一个异常,在不同 Controller 表现不一样?
这些问题,用"注解怎么写"是回答不了的,只能从源码解释。
2. ExceptionHandlerExceptionResolver 是谁在干活
在上一篇中我们已经知道,异常最终会进入这条责任链:
ExceptionHandlerExceptionResolver
→ ResponseStatusExceptionResolver
→ DefaultHandlerExceptionResolver
而 @ExceptionHandler 和 @ControllerAdvice 的真正执行者,就是:
ExceptionHandlerExceptionResolver
3. ExceptionHandlerExceptionResolver 的核心职责
从类注释就能看出它的定位:
java
/**
* An {@link HandlerExceptionResolver} that resolves exceptions through
* {@link ExceptionHandler} methods.
*/
它只做一件事:
找到能处理当前异常的 @ExceptionHandler 方法,并执行它
4. @ExceptionHandler 方法是如何被扫描的
4.1 初始化阶段:扫描所有异常处理方法
在容器启动阶段,ExceptionHandlerExceptionResolver 会执行初始化逻辑:
java
public void afterPropertiesSet() {
initExceptionHandlerAdviceCache();
}
4.2 扫描 @ControllerAdvice
java
private void initExceptionHandlerAdviceCache() {
List<ControllerAdviceBean> adviceBeans =
ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
}
关键点:
- 扫描整个 Spring 容器
- 找出所有标注了 @ControllerAdvice 的 Bean
- 封装为 ControllerAdviceBean
4.3 ControllerAdvice 的"作用范围"不是全局那么简单
@ControllerAdvice 支持条件匹配:
java
@ControllerAdvice(
basePackages = "com.example.web",
annotations = RestController.class
)
源码中通过 HandlerTypePredicate 判断是否适用当前 Controller。
👉 这也是为什么:
有些 Advice 明明存在,却对某些 Controller 不生效
5. @ExceptionHandler 方法是如何被缓存的
5.1 ExceptionHandlerMethodResolver
每一个 Controller 或 Advice,都会对应一个解析器:
java
new ExceptionHandlerMethodResolver(beanType);
它会:
- 扫描所有方法
- 找出 @ExceptionHandler
- 建立异常类型 → 方法的映射关系
5.2 一个方法可以处理多个异常
java
@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
public ErrorResponse handle(Exception e) {}
源码中会把它拆解成多条映射关系。
5.3 异常匹配是"最近优先"
如果存在继承关系:
text
RuntimeException
└── IllegalArgumentException
IllegalArgumentException 会优先匹配,而不是父类异常。
这是通过 ExceptionDepthComparator 实现的。
6. 异常发生时,Resolver 是如何找方法的
异常真正发生后,会进入:
java
protected ModelAndView doResolveHandlerMethodException(...)
核心逻辑:
- 先找 Controller 内部的 @ExceptionHandler
- 再找全局 @ControllerAdvice
- 找到就执行,找不到返回 null
6.1 Controller 内部优先于 ControllerAdvice
这是一个非常重要的优先级规则:
局部异常处理 > 全局异常处理
源码中体现为:
java
getExceptionHandlerMethod(handlerMethod, exception)
先基于当前 Controller 查找。
7. @ExceptionHandler 方法是如何被执行的
一旦找到目标方法,Spring 会把它包装成:
java
ServletInvocableHandlerMethod
这个类你在 MVC 参数解析中已经见过。
7.1 参数是如何自动注入的
java
@ExceptionHandler(Exception.class)
public ErrorResponse handle(
Exception ex,
HttpServletRequest request
) {}
参数解析复用的正是:
- HandlerMethodArgumentResolver 体系
👉 异常处理方法,本质上也是一个 MVC 方法。
7.2 返回值是如何写入响应的
返回值处理同样复用:
- HandlerMethodReturnValueHandler
- HttpMessageConverter
所以你可以:
- 返回对象
- 返回 ResponseEntity
- 返回 void
8. 为什么 @ResponseBody 能生效
在 Spring Boot 中,常见写法是:
java
@RestControllerAdvice
它本质等价于:
java
@ControllerAdvice
@ResponseBody
@ResponseBody 的解析发生在:
- 返回值处理阶段
- 由 RequestResponseBodyMethodProcessor 完成
9. 多个 @ControllerAdvice 的执行顺序
9.1 顺序规则
优先级由以下规则决定:
- @Order
- Ordered 接口
- 默认顺序(最低优先级)
java
@Order(1)
@RestControllerAdvice
class BizExceptionAdvice {}
@Order(2)
@RestControllerAdvice
class SystemExceptionAdvice {}
9.2 为什么顺序很重要
因为:
- 第一个匹配成功的异常处理方法会直接返回
- 后续 Advice 不再执行
10. 常见"异常不生效"的根本原因
| 现象 | 根本原因 |
|---|---|
| Advice 不生效 | basePackages 不匹配 |
| 方法不进 | 异常类型不匹配 |
| 被吞掉 | 前面 Resolver 已处理 |
| 返回 500 | Resolver 返回 null |
这些问题,只有看源码才能彻底理解。
11. 异常处理方法执行流程图
是
否
是
否
异常抛出
ExceptionHandlerExceptionResolver
Controller 内是否有 @ExceptionHandler
执行 Controller 内方法
是否匹配 ControllerAdvice
执行 Advice 方法
返回 null
图1 @ExceptionHandler 方法解析与执行流程
12. 本篇关键认知升级
到这里,你应该已经清楚:
- @ExceptionHandler 并不是"魔法"
- ControllerAdvice 不是"全局兜底",而是有严格匹配规则
- 异常处理方法本质上是一个 MVC Handler
- 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
结束语

掘金点击访问Qiuner CSDN点击访问Qiuner GitHub点击访问Qiuner Gitee点击访问Qiuner
| 专栏 | 简介 |
|---|---|
| 📊 一图读懂系列 | 图文并茂,轻松理解复杂概念 |
| 📝 一文读懂系列 | 深入浅出,全面解析技术要点 |
| 🌟持续更新 | 保持学习,不断进步 |
| 🎯 人生经验 | 经验分享,共同成长 |
你好,我是Qiuner. 为帮助别人少走弯路而写博客
如果本篇文章帮到了你 不妨点个赞 吧~ 我会很高兴的 😄 (^ ~ ^) 。想看更多 那就点个关注吧 我会尽力带来有趣的内容 😎。
代码都在Github或Gitee上,如有需要可以去上面自行下载。记得给我点星星哦😍
如果你遇到了问题,自己没法解决,可以去我掘金评论区问。CSDN评论区和私信消息看不完 掘金消息少一点.
| 上一篇推荐 | 链接 |
|---|---|
| Java程序员快又扎实的学习路线 | 点击该处自动跳转查看哦 |
| 一文读懂 AI | 点击该处自动跳转查看哦 |
| 一文读懂 服务器 | 点击该处自动跳转查看哦 |
| 2024年创作回顾 | 点击该处自动跳转查看哦 |
| 一文读懂 ESLint配置 | 点击该处自动跳转查看哦 |
| 老鸟如何追求快捷操作电脑 | 点击该处自动跳转查看哦 |
| 未来会写什么文章? | 预告链接 |
|---|---|
| 一文读懂 XX? | 点击该处自动跳转查看哦 |
| 2025年终总结 | 点击该处自动跳转查看哦 |
| 一图读懂 XX? | 点击该处自动跳转查看哦 |


掘金点击访问Qiuner CSDN点击访问Qiuner GitHub点击访问Qiuner Gitee点击访问Qiuner
| 专栏 | 简介 |
|---|---|
| 📊 一图读懂系列 | 图文并茂,轻松理解复杂概念 |
| 📝 一文读懂系列 | 深入浅出,全面解析技术要点 |
| 🌟持续更新 | 保持学习,不断进步 |
| 🎯 人生经验 | 经验分享,共同成长 |
你好,我是Qiuner. 为帮助别人少走弯路而写博客
如果本篇文章帮到了你 不妨点个赞 吧~ 我会很高兴的 😄 (^ ~ ^) 。想看更多 那就点个关注吧 我会尽力带来有趣的内容 😎。
代码都在Github或Gitee上,如有需要可以去上面自行下载。记得给我点星星哦😍
如果你遇到了问题,自己没法解决,可以去我掘金评论区问。CSDN评论区和私信消息看不完 掘金消息少一点.
| 上一篇推荐 | 链接 |
|---|---|
| Java程序员快又扎实的学习路线 | 点击该处自动跳转查看哦 |
| 一文读懂 AI | 点击该处自动跳转查看哦 |
| 一文读懂 服务器 | 点击该处自动跳转查看哦 |
| 2024年创作回顾 | 点击该处自动跳转查看哦 |
| 一文读懂 ESLint配置 | 点击该处自动跳转查看哦 |
| 老鸟如何追求快捷操作电脑 | 点击该处自动跳转查看哦 |
| 未来会写什么文章? | 预告链接 |
|---|---|
| 一文读懂 XX? | 点击该处自动跳转查看哦 |
| 2025年终总结 | 点击该处自动跳转查看哦 |
| 一图读懂 XX? | 点击该处自动跳转查看哦 |