Spring Boot 全局异常处理策略设计之@ExceptionHandler 与 @ControllerAdvice 生效原理源码解析
作者:Qiuner
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(...)
核心逻辑:
- 先找 Controller 内部的 @ExceptionHandler
- 再找全局 @ControllerAdvice
- 找到就执行,找不到返回 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 顺序规则
优先级由以下规则决定:
- @Order
- Ordered 接口
- 默认顺序(最低优先级)
@Order(1)
@RestControllerAdvice
class BizExceptionAdvice {}
@Order(2)
@RestControllerAdvice
class SystemExceptionAdvice {}9.2 为什么顺序很重要
因为:
- 第一个匹配成功的异常处理方法会直接返回
- 后续 Advice 不再执行
10. 常见“异常不生效”的根本原因
| 现象 | 根本原因 |
|---|---|
| Advice 不生效 | basePackages 不匹配 |
| 方法不进 | 异常类型不匹配 |
| 被吞掉 | 前面 Resolver 已处理 |
| 返回 500 | Resolver 返回 null |
这些问题,只有看源码才能彻底理解。
11. 异常处理方法执行流程图

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