关键在于包装异常时必须显式传递原始异常作为cause,否则getcause()返回null导致根因丢失;所有自定义异常须提供带cause的构造器;正确写法是throw new serviceexception("msg", e);aop中应用while循环安全获取root cause;警惕spring等框架的伪null cause,优先用exceptionutils.getrootcause()。

关键在于包装时必须显式传递原始异常作为 cause,而不是仅拼消息或调用无参构造器。否则 getCause() 会返回 null,根因彻底丢失。
所有自定义异常类必须提供带 cause 的构造器
继承 RuntimeException 或 Exception 时,不能只写一个 public XxxException(String msg)。必须至少补充:
-
public XxxException(String msg, Throwable cause)—— 内部调用super(msg, cause) -
public XxxException(Throwable cause)—— 内部调用super(cause) - 无参和仅 message 的构造器可选,但它们不应干扰 cause 传递逻辑
包装异常时禁止只传字符串或只传 Throwable
这两类写法看似相似,实则后果不同:
- ❌ 错误:throw new ServiceException("DB error") —— cause 为 null,原始异常完全消失
- ❌ 错误:throw new RuntimeException(e) —— 虽然 e 成为 cause,但新异常自身栈轨迹为空,printStackTrace() 默认不显示原始堆栈
- ✅ 正确:throw new ServiceException("DB error", e) —— 消息 + cause 同时保留,getCause() 可取,日志框架也能自动展开链
在 AOP 或全局异常处理器中安全展开 cause 链
不要依赖递归方法,避免栈溢出或循环引用;用 while 循环逐层获取 root cause:
- 先判空:
if (e == null) return; - 用循环代替递归:
Throwable root = e; while (root.getCause() != null && root.getCause() != root) { root = root.getCause(); } - 日志记录时传入 root(或完整异常对象),而非
e.getMessage()或e.toString()
警惕 Spring 等框架的“伪 null” cause
某些 Spring 异常(如 TransactionSystemException)的 getCause() 返回 null,但真实原因藏在 getRootCause() 或专属字段(如 getOriginalException())里:
- 遇到 getCause() == null,别急着断定链已断,先查该异常类文档或源码
- 优先使用
ExceptionUtils.getRootCause(e)(Apache Commons)这类通用工具 - 在日志中用
log.error("xxx", e),而非手动 toString(),让日志框架自动处理嵌套
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











