关键是要用带cause构造器包装异常以保留原始堆栈,自定义异常需提供throwable cause构造器,日志必须传整个异常对象,避免静默吞掉或中途截断。

关键不是“重新抛出”,而是把原始异常作为 cause 显式嵌入新异常,让堆栈里出现清晰的 Caused by 层级,从而一眼锁定最开始出错的地方。
用带 cause 的构造器包装,别拼消息
捕获底层异常后,要转成业务异常,必须调用支持 Throwable cause 参数的构造器:
- ✅ 正确:
throw new OrderException("下单失败", e);(e 是 catch 到的原始异常) - ❌ 错误:
throw new OrderException("下单失败: " + e.getMessage());(丢掉堆栈、丢掉 cause) - ❌ 错误:
throw new OrderException("下单失败").initCause(e);(不推荐,易出 IllegalStateException)
自定义异常必须支持 cause 构造器
所有业务异常类都要提供含 Throwable cause 的公有构造器,并在内部调用 super(message, cause):
- 确保
getCause()能返回原始异常 - 避免写成只接受
String的单参构造器 - 标准异常(如
IOException、RuntimeException)已内置该能力,直接复用即可
日志记录必须传整个异常对象
只打 e.getMessage() 或拼字符串,等于主动抹掉根源:
- ✅ 正确:
log.error("支付回调处理异常", e);(SLF4J 自动输出完整链) - ❌ 错误:
log.error("支付回调处理异常: " + e.getMessage());(无堆栈、可能泄露敏感信息) - 验证方式:运行时看
e.printStackTrace()输出里是否有缩进的 Caused by: 行
避免静默吞掉或中途截断
这些操作会让原始异常彻底消失:
- catch 后只打日志、不 re-throw,也不包装
- finally 块里执行
return或throw,会覆盖 try/catch 中的异常 - 用
Optional.orElseThrow(() -> new XxxException())时,没把已有异常设为 cause - 多层无意义包装,比如
AException → BException → CException → SQLException,中间两层没新增上下文
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











