Java中包装底层异常的核心目的是保留原始错误信息并提供业务语义清晰的异常层次,需用带cause构造器、分层封装、增强消息上下文、慎用suppressed。
Java中包装底层异常的核心目的是保留原始错误信息的同时,提供更清晰、更符合业务语义的异常层次。关键不是简单地“套一层”,而是让异常既可追溯(有完整栈轨迹和cause链),又易理解(有明确业务含义和处理指引)。
最基础也最重要的方式是使用带Throwable cause参数的异常构造器。这能确保原始异常不丢失,并可通过getCause()和打印堆栈自然展现嵌套关系。
SQLException),也不要只用new RuntimeException("xxx")丢掉causethrow new ServiceException("订单创建失败", e);,其中e
是捕获的SQLException
不要所有地方都用RuntimeException。合理分层能让调用方明确知道:这是需要重试的系统问题?还是应提示用户的校验失败?
DatabaseAccessException、RemoteCallException,继承RuntimeException,表示非预期故障,通常需日志+告警InsufficientBalanceException、OrderAlreadyPaidException,表达明确业务规则违反,调用方常需主动处理(跳转页面、弹窗提示)原始异常的消息往往太泛(如“Connection refused”),而cause又藏在堆栈深处。应在封装时把关键业务上下文注入到新异常消息中。
new ServiceException("操作失败", e)
new ServiceException("支付订单[" + orderId + "]时连接支付网关超时", e)
traceId),方便全链路排查:"[trace-abc123] 创建用户失败:邮箱已存在"
addSuppressed()适合“主异常发生时,清理资源又抛出次异常”的场景(如try-with-resources)。但日常封装中滥用会污染堆栈、增加分析难度。
try (Resource r = open()) { ... } catch (IOException e) { throw new ServiceException("读取配置失败", e); } —— 此时无需addSuppressed,cause已足够不复杂但容易忽略:每次封装异常前,花两秒想清楚——这个异常最终会被谁看到?他需要知道什么?要不要重试?要不要提示用户?答案自然会告诉你该怎么包。