Appearance
Spring 全局异常处理怎么设计:别只会 try-catch,要把错误边界收口
很多项目一开始写异常处理时,最常见的方式就是:
- Controller 里自己
try-catch - 出错了直接返回字符串
- 或者统一
catch Exception
这种写法短期能跑,但项目一大就会很乱。
先说结论
更稳妥的 Spring 异常处理,通常要把这几件事分开:
- 业务异常和系统异常分层
- 返回给前端的内容和记录到日志的内容分开
- Controller 不负责到处
try-catch - 用统一异常处理把错误边界收口
一、为什么不要在 Controller 到处写 try-catch
因为这样会带来三个问题:
- 接口返回格式不统一
- 日志记录不一致
- 新同事很难知道哪里该怎么处理
真正成熟一点的后端项目,Controller 更适合只关心:
- 参数接收
- 调用 Service
- 返回结果
异常边界应该统一放到全局去处理。
二、业务异常和系统异常一定要分开
这一步非常关键。
业务异常通常表示:
- 参数不符合业务规则
- 状态不满足操作条件
- 库存不足
- 重复提交
系统异常通常表示:
- 空指针
- 数据库连接失败
- 下游服务超时
- 程序 Bug
这两类异常对前端和日志来说,处理方式完全不一样。
三、统一异常处理到底在做什么
本质上是在回答两个问题:
- 这个错误应该返回什么给调用方
- 这个错误应该如何记录、告警和排查
所以全局异常处理器不是“把异常吞掉”,而是:
- 把不一致的错误出口收敛起来
四、一个更实用的设计思路
1. 定义统一返回结构
比如统一返回:
- 是否成功
- 业务码
- 提示信息
- 数据体
这样前端和调用方更容易稳定处理。
2. 业务异常自定义
业务异常一般要明确:
- 错误码
- 可读消息
- 是否需要展示给用户
3. 参数校验异常单独处理
这类异常很高频,最好单独收口,否则前端看到的体验会很差。
4. 兜底系统异常统一处理
对未知异常统一兜底,避免把堆栈直接暴露给前端。
五、日志该怎么记
这里特别容易踩坑。
更稳妥的方式通常是:
- 业务异常可以少打或按需打
- 系统异常要带请求上下文和关键参数
- 不要同一异常重复打印多次
很多项目日志爆炸,不是异常多,而是:
- Controller 打一次
- 全局处理器再打一次
- 网关或调用链再打一次
六、最容易踩的坑
1. catch Exception 后直接返回成功结构
这会把问题藏得很深。
2. 所有错误都返回同一个提示
调用方根本分不清:
- 是参数错了
- 还是服务真的出故障了
3. 把数据库错误原样返回给前端
这既不安全,也不友好。
一句话总结
Spring 全局异常处理真正要做的,不是“把所有异常抓住”,而是把:
- 错误分层
- 返回统一
- 日志收口
- 排查留痕
这四件事同时做好。