可维护的异常处理体系核心在于明确异常语义、归属与上下文。需统一业务异常分类,各层职责分明:DAO包装原生异常,Service抛出业务异常,Controller统一响应;结合MDC增强日志上下文,区分4xx/500状态码,拒绝用异常控制流程。

设计可维护的异常处理体系,核心不是“怎么捕获”,而是“异常代表什么”和“谁该对它负责”。统一分类、明确归属、上下文完整,这三点决定了异常能否被快速定位、合理响应、长期演进。
用业务语义定义异常类型
避免直接 throw new RuntimeException("库存不足") 这类模糊表达。每个可预期的业务失败场景,都应有专属异常类,比如 InsufficientStockException、InvalidOrderStatusException。
- 所有自定义异常继承同一基类(如 BusinessException),便于统一拦截和处理
- 构造时强制传入错误码(如 "ORDER_002")和面向用户的提示消息,不依赖堆栈推断语义
- 错误码按模块+序号分层设计(如用户模块以 "USER_XXX" 开头),方便前端识别和运营归因
分层职责必须清晰
DAO 层不抛 SQLException,Controller 层不写 try-catch 处理库存逻辑——各层只做自己该做的事。
- DAO 层:捕获原生数据库异常(如 SQLTimeoutException),包装成带业务语义的 BusinessException,并保留原始 cause,供排查用
- Service 层:专注编排逻辑,只在必要时主动抛出业务异常(如校验失败、状态冲突),不处理底层异常
- Controller 层:零业务判断,仅通过 @RestControllerAdvice 统一翻译异常为 HTTP 响应;不重试、不补偿、不记录业务日志
全局兜底 + 上下文增强
异常一旦抛出,就要确保它“说得清、查得到、能复现”。
立即学习“Java免费学习笔记(深入)”;
- 使用 MDC(Mapped Diagnostic Context)在请求入口注入 traceId、userId 等关键字段,让每条异常日志自带上下文
- @ExceptionHandler 中记录 warn 级别日志,包含错误码、用户提示、关键参数(如订单号、SKU),但不打完整堆栈(留给兜底 handler)
- 对 Exception 或 Throwable 的兜底处理,必须打印完整堆栈并返回 500,但要加开关控制——仅在线上开启,开发环境直接暴露堆栈
拒绝“异常即失败”的思维惯性
不是所有异常都需要中断流程。有些是正常业务分支,应设计为可预期、可恢复的信号。
- 例如支付超时,可抛出 PaymentTimeoutException,上游调用方据此触发降级或异步重试,而非当作系统故障报警
- 避免用异常控制流程(如用 NullPointerException 判断对象为空),该用 if 就用 if
- 对外 API 返回体中,业务异常对应 4xx 状态码(如 400/422),系统异常才用 500,前端据此区分展示策略


















