异常链需显式构造,必须用带throwable cause参数的构造函数传递原始异常,否则上下文丢失;自定义异常须提供双参构造器;日志中出现“caused by:”表明链生效。
异常链不是自动发生的,必须显式构造
Java **不会自动把捕获到的异常“塞进”新异常里**。你写 throw new BusinessException("下单失败"),原始的 SQLException 或 TimeoutException 就彻底丢了——堆栈里只剩一句空洞描述,日志中也不会出现 Caused by:。真正起作用的,是你主动把旧异常作为 cause 传进去。
- ✅ 正确做法:用带
Throwable cause参数的构造函数,如new BusinessException("库存扣减失败", e) - ❌ 错误做法:只取
e.getMessage()构造新异常,或漏掉e直接抛新异常 - ⚠️ 注意:
cause本身也可以有getCause(),但链深超过 2~3 层反而难读,别为了“链”而链
自定义异常必须支持双参构造函数
如果你写了 MyOrderException extends RuntimeException,却只实现了 MyOrderException(String message),那别人就无法用它构建异常链——哪怕调用方想传 e,编译都过不去。
- ✅ 必须提供:
public MyOrderException(String message, Throwable cause) { super(message, cause); } - ❌ 不要省略:不写这个构造器,等于主动关闭了异常上下文传递能力
- ? 小技巧:IDE(如 IntelliJ)可一键生成带 cause 的构造器,别手敲漏掉
super(...)
什么时候非用 initCause()?基本不用
initCause() 是给老代码兜底用的:比如你依赖的某个 SDK 里的异常类,是 JDK 1.3 时代写的,压根没提供双参构造器。现代 Java(1.4+)所有标准异常和你自己写的异常,都应该走构造函数路线。