应继承 runtimeexception 定义业务异常类,配结构化错误码枚举,提供三种构造函数保障异常链完整,并用 context 字段存储上下文而非拼接 message。

直接用 RuntimeException("订单不存在") 是最常见也最容易埋雷的做法。它把业务拒绝当成程序崩溃,让调用方无法区分“用户输错ID”和“数据库连不上”,日志里只剩字符串,前端靠猜提示,监控没法按码统计——这不是异常处理,是信息擦除。
继承 RuntimeException,而非 Exception
业务异常是流程中的正常分支,不是系统故障。继承 RuntimeException 才符合语义:
- 不强制下游
try-catch或throws,避免污染接口契约 - Spring 的
@Transactional默认对RuntimeException回滚,但你可以通过异常类型或字段控制是否真回滚(比如“余额不足”不该回滚已查出的余额) -
@RestControllerAdvice默认只捕获非受检异常,统一拦截才有效 - 只有极少数集成场景(如银行 SDK 签名失败)才考虑受检异常,日常业务一律用运行时异常子类
错误码必须结构化,且与协议解耦
别用字符串拼错码,也别混 HTTP 状态码。定义纯业务语义的枚举:
ORDER_NOT_FOUND(1001)INSUFFICIENT_BALANCE(1002)COUPON_EXPIRED(1003)
好处很实在:RPC 调用可透传该码;前端按 code 映射提示文案;国际化只需换 message,码不变;监控系统能按码统计错误率。HTTP 状态码由 Controller 层统一映射(如 ORDER_NOT_FOUND → 404),和异常类无关。
提供三类构造函数,保障异常链完整
只写一个构造函数,等于主动丢掉调试线索。必须覆盖三种典型场景:
-
BizException(ErrorCode code, String message):用于简单业务拒绝,如“订单不存在” -
BizException(String message, Throwable cause):用于包装底层异常(如 DAO 抛出的SQLException),保留原始堆栈链 -
BizException(ErrorCode code, String message, Throwable cause):最完整形态,既带业务语义又带根因,排查时一眼分清是规则拦截还是依赖故障
每个构造函数都必须调用 super(message, cause) 或 super(message),否则 getMessage() 返回 null 或空字符串,日志第一行就失效。
用 context 字段存上下文,别塞进 message
用户 ID、订单号、请求参数这些现场信息,应放进 Map<string object> context</string> 字段,而不是拼进 message 字符串:
-
message是给人看的,context是给系统用的——日志框架、链路追踪、告警规则可直接提取结构化字段 - 避免日志里出现“订单不存在,用户ID=123456,订单号=ORD20260617001”,既难解析又易泄露敏感信息
- 生产环境日志可仅打印
code+message+requestId,调试时再展开context和完整堆栈
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











