java异常链的核心是保留原始异常上下文,底座异常需用带cause构造函数封装、中间层增强语义不破坏链路、统一处理器遍历cause定位根因并分级响应,日志与监控须完整保留链路。

Java中异常链(Exception Chaining)的核心是保留原始异常的上下文信息,避免“吞掉”底层错误。在企业级脚手架中,底座异常(如DAO层数据库异常、RPC调用失败、配置加载异常等)不能简单抛出或包装成通用错误码,而应通过异常链逐层传递关键堆栈和业务语义,再由统一异常处理器做分级响应。
底座异常需保留原始根因
脚手架的DAO、SDK、Config等底座模块抛出异常时,应优先使用带cause参数的构造函数,而非新建一个无关联的新异常。例如MyBatis执行SQL失败时,不应只抛BusinessException("查询失败"),而应:
- 捕获
SQLException后,用new DaoException("用户查询异常", sqlEx)封装,确保getCause()仍指向原始SQL异常 - 避免调用
ex.printStackTrace()或log.error("", ex)后静默返回,这会切断异常链 - 底座异常类建议继承
RuntimeException,并显式提供public XxxException(String msg, Throwable cause)构造方法
中间层不破坏链路,只增强语义
Service或Gateway层对底座异常做二次封装时,目标不是替换异常,而是补充业务上下文。例如RPC调用失败后:
- 可创建
RemoteInvokeException("调用用户中心获取头像失败", remoteEx),其中remoteEx是原始Feign/HttpClient异常 - 禁止使用
new RuntimeException("调用失败", null)或throw new RuntimeException(msg)——这会丢失cause - 可在异常类中增加字段(如
traceId、serviceCode),但必须通过构造函数传入,并在toString()或getDetailMessage()中体现,不影响getCause()
统一异常处理器提取链式根因
全局ExceptionHandler需识别异常链中最关键的原始异常类型,而非只看顶层异常类名。例如:
- 定义常见根因类型列表:
SQLException.class、ConnectException.class、JsonProcessingException.class - 遍历
Throwable.getCause()直到找到第一个匹配类型,或到达null - 根据根因类型映射HTTP状态码(如SQL异常→500,网络超时→503)和错误码(如
ERR_DB_TIMEOUT),同时将原始异常消息摘要写入响应体的debugInfo字段(生产环境可开关)
日志与监控中保留完整链路
底座异常一旦进入链路,日志记录和APM上报必须携带完整cause链。推荐做法:
- SLF4J打印时直接
log.error("订单创建失败", ex),不要log.error("订单创建失败: {}", ex.getMessage()) - ELK或Sentry中配置解析
throwable字段,支持展开嵌套cause(Logback默认支持,需确认appender配置) - 在异常类中重写
fillInStackTrace()需谨慎——除非明确要屏蔽某一层堆栈,否则默认不重写,保证printStackTrace()能输出全链
异常链不是加几层包装,而是构建一条可追溯、可分类、可响应的错误脉络。脚手架底座的每个异常出口,都应是一扇通往根因的门,而不是一堵墙。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











