@[toc]

Spring Boot 全局异常处理策略设计(一):异常不只是 try-catch
1. 为什么异常值得单独写一整个系列
在多数业务代码中,异常往往被当作一种"不得不写的语法":
php
try {
service.process();
} catch (Exception e) {
log.error("error", e);
}
但在真实的 Web 系统中,异常从来不是一个语法问题,而是一个系统失控时的兜底机制。
一次 HTTP 请求,往往会经历如下链路:
- Filter
- Interceptor
- Controller
- 参数绑定与校验
- Service
- DAO
- 事务
- AOP 代理
- JSON 序列化
异常可以在任何一个环节出现,而且一旦出现,就会"逆着调用栈"向上冒泡,最终由框架决定:
- 是否回滚事务
- 是否返回 500
- 返回什么格式
- 是否记录堆栈
- 是否暴露错误信息给前端
因此,异常不是"边角料",而是整个调用链的终点汇合处。
2. Java 异常模型的关键认知(只讲对后面有用的)
2.1 Throwable 体系结构
php
Throwable
├── Error
└── Exception
├── RuntimeException
└── Checked Exception
- Error:JVM 级错误,应用通常无能为力
- Exception:应用级异常
- RuntimeException:Spring 默认认为这是"不可恢复异常"
- Checked Exception:需要显式声明和处理
Spring 事务为什么默认只对 RuntimeException 回滚? 这个问题在后面的事务异常章节会专门展开。
2.2 异常传播是"反向调用链"
正常调用是:
Controller → Service → DAO
异常传播是:
DAO 抛异常 → Service → Controller → 框架
谁最后接住异常,谁就拥有最终解释权。
3. 为什么 Web 系统不能到处 try-catch
3.1 try-catch 的三个常见问题
- 吞异常,导致问题被掩盖
- 重复代码,Controller 层异常处理泛滥
- 破坏事务回滚逻辑
例如:
typescript
@Transactional
public void createOrder() {
try {
saveOrder();
} catch (Exception e) {
log.error("error", e);
}
}
这个代码看起来稳健,实际上事务已经无法回滚。
3.2 异常必须"集中处理"
在 Web 架构中,有一个非常重要的设计原则:
异常应该在"边界层"统一处理,而不是在业务层消化。
Spring MVC 正是基于这个原则,设计了一整套异常处理机制。
4. Spring MVC 的异常处理总体设计思想
4.1 正常流程 vs 异常流程
正常流程:
请求 → Handler → 返回值 → 响应
异常流程:
请求 → Handler → 抛异常 → 异常解析 → 响应
Spring MVC 的核心设计点在于:
异常不是 if-else 分支,而是一条独立的处理链路。
4.2 异常处理在 DispatcherServlet 中的位置
DispatcherServlet 是整个 MVC 的"总控中枢"。
在其核心方法 doDispatch 中,异常被统一捕获:
php
try {
// 查找 Handler 并执行
} catch (Exception ex) {
dispatchException = ex;
}
这意味着:
- Controller 不需要感知异常如何返回
- 框架会统一接管异常
5. Spring MVC 的三种基础异常处理方式
5.1 直接抛出异常(推荐)
typescript
@GetMapping("/order")
public Order getOrder() {
throw new IllegalArgumentException("参数错误");
}
异常会交给框架处理,而不是在 Controller 内部解决。
5.2 @ExceptionHandler:局部异常处理
kotlin
@RestController
public class OrderController {
@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArg(Exception e) {
return e.getMessage();
}
}
特点:
- 只对当前 Controller 生效
- 适合非常局部的异常场景
5.3 @ControllerAdvice:全局异常处理(重点)
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ErrorResponse handle(Exception e) {
return new ErrorResponse("500", e.getMessage());
}
}
这是 Spring Boot 项目中最常见的异常处理入口。
6. @ControllerAdvice 的设计价值
6.1 为什么它是"全局异常处理"的核心
@ControllerAdvice 本质上解决了三个问题:
- 异常集中管理
- 返回格式统一
- 与业务逻辑解耦
6.2 多个 ControllerAdvice 的顺序问题
Spring 支持定义多个全局异常处理器:
less
@Order(1)
@RestControllerAdvice
class BizExceptionHandler {}
@Order(2)
@RestControllerAdvice
class SystemExceptionHandler {}
优先级越小,越先执行。
7. 异常处理的第一版架构形态
在"入门阶段",一个相对合理的异常架构通常是:
css
Controller
↓
抛异常
↓
@ControllerAdvice
↓
统一错误响应
对应的返回结构示例:
css
{
"code": "SYSTEM_ERROR",
"message": "系统异常,请稍后再试"
}
8. 异常处理流程图(概览)
php
throw Exception
HTTP Request
DispatcherServlet
Controller
Exception Resolver
@ExceptionHandler / ControllerAdvice
HTTP Response
图1 Spring MVC 异常处理基本流程图
9. 本篇小结(从入门视角看异常)
到这里,我们只做了三件事:
- 纠正"异常只是 try-catch"的认知
- 明确异常是 Web 系统的统一出口
- 理解 Spring MVC 为什么要集中处理异常
但我们还没有回答几个关键问题:
- 异常是如何一步步被解析的?
- 为什么 @ExceptionHandler 能生效?
- Spring Boot 的 /error 是干什么的?
- 为什么有些异常进不了 ControllerAdvice?
👉 这些问题,都需要进入源码层面才能解释清楚。
参考资料
- Spring Framework Reference -- Exception Handling docs.spring.io/spring-fram...
- Spring MVC 源码(DispatcherServlet) github.com/spring-proj...
结束语

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