异常转换是为了在不同抽象层级间建立语义清晰、职责分明的错误契约,避免底层技术异常污染业务逻辑,提升可读性、可维护性与决策能力。

异常转换不是简单地把一个异常换成另一个,而是为了在不同抽象层级之间建立语义清晰、职责分明的错误契约。它让底层技术细节不污染上层业务逻辑,也让调用方能基于明确的业务含义做决策。
为什么需要异常转换
直接暴露底层异常(比如 SQLException、IOException)会导致几个问题:
- 上层模块被迫依赖具体技术实现,违反了“面向接口编程”原则
- 业务代码要写一堆和数据库、IO、网络无关的 catch 块,可读性和可维护性差
- 原始异常携带的信息对业务无意义,比如 “Connection refused” 无法告诉用户“下单失败,请稍后重试”
- 检查型异常强制传播,容易引发“throws 堆砌”,破坏 API 的简洁性
典型转换场景与做法
常见于 DAO 层 → Service 层、第三方 SDK → 业务门面、HTTP 客户端 → 领域服务等边界处。
- DAO 层抛出 SQLException → Service 层转为 DataAccessException:隐藏 JDBC 细节,统一数据访问异常语义
- 调用支付网关抛出 TimeoutException → 转为 PaymentTimeoutException:附带 paymentId、canRetry 标志,供上层决定是否重试或查单
- FeignClient 抛出 ConnectException → 转为 RemoteServiceUnavailableException:标记服务不可用,触发熔断或降级策略
- JSON 解析失败抛出 JsonProcessingException → 转为 InvalidRequestException:向 API 层表明是客户端输入问题,返回 400 状态码
转换时的关键设计要点
一次好的转换不是换名字,而是加信息、明意图、控传播。
- 新异常必须继承合适的基类(如 RuntimeException 表示非受检,或自定义受检异常)
- 保留原始异常作为 cause,确保调用栈和根因可追溯
- 构造时注入关键上下文(如订单号、请求 ID、重试策略标识)
- 避免在转换中吞掉异常或只留空消息;message 应体现业务影响,而非技术现象
- 不要在 service 方法里 catch 后静默吞掉再 throw 新异常——那会丢失原始堆栈;应直接 throw new XxxException("msg", e)
配合全局异常处理器落地
转换后的异常需被统一拦截,生成一致响应格式,并区分日志级别:
- BusinessException 类异常 → 记录 warn 日志,返回用户友好提示(如“库存不足”)
- SystemException 类异常 → 记录 error 日志 + traceId,返回通用错误码,触发告警
- 原始 Error(如 OutOfMemoryError)不参与转换,由 JVM 或容器层捕获处理
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











