异常转换的核心是将底层受检异常(如sqlexception)按分层语义包装为业务异常,必须通过cause链保留原始堆栈,赋予明确业务消息,避免技术细节泄露、类型泛化或重复包装。

异常转换的核心,是把底层抛出的受检异常(checked exception)用更贴近业务语义、更易被上层理解的方式重新抛出,而不是简单地层层往上扔或吞掉。
为什么要包装,而不是直接抛?
Java 的受检异常强制调用方处理,但底层异常(比如 SQLException、IOException)往往和业务逻辑无关。如果让 Service 层直接暴露 SQLException,就等于把数据访问细节泄露给了业务层——这违反了分层设计原则,也增加了耦合。
- 屏蔽技术细节:调用方不需要知道你用的是 JDBC 还是 JPA
- 统一错误语义:比如“用户不存在”应表达为 UserNotFoundException,而不是 EmptyResultDataAccessException
- 便于错误分类与处理:业务异常、系统异常、参数异常可分别建模,下游能按类型做差异化响应(如重试、告警、前端提示)
怎么包装才不算“套壳”?
包装不是套个新类名就完事。关键在于保留原始异常的上下文,并赋予它业务含义。
使用 OpenAI Codex CLI 处理编码任务。触发词:codex、code review、fix CI、refactor code、implement feature、coding agent、gpt-5-codex。Clawdbot 可将编码工作委托给 Codex CLI 作为子代理或直接工具。
- 用 cause 构造:新异常必须通过
new BizException("下单失败", e)把原始异常设为 cause,否则堆栈丢失,排查困难 - 消息要有信息量:避免 “操作失败”,改用 “库存不足,商品 ID=1024 当前余量为 0”
- 避免过度包装:同一异常链中不建议连续包装两次(如 SQLException → DaoException → ServiceException),选最贴近当前层语义的一层即可
哪些场景适合包装?
不是所有异常都要转。重点在“跨边界”和“语义失配”时出手:
- DAO 层 → Service 层:把数据库异常转为业务异常(如 OrderAlreadyPaidException)
- 远程调用(RPC/HTTP):把 ConnectException 或 TimeoutException 包装成 ThirdPartyServiceUnavailableException
- 文件/IO 操作:把 FileNotFoundException 转为 ResourceNotFoundException,隐藏路径细节
别踩这些坑
常见反模式会削弱异常的价值:
- 吃掉异常只打日志:
catch (SQLException e) { log.error(...); return null; }—— 调用方完全不知失败,后果不可控 - 丢弃 cause:只写
throw new BusinessException("失败"),原始堆栈消失 - 泛化异常类型:全用 RuntimeException 或自定义的 AppException 一包到底,失去类型区分能力
- 在 finally 块里抛新异常:可能掩盖真正的 root cause










