异常包装的核心目标是将底层技术异常转化为语义化的业务异常,如OrderNotFoundException替代EmptyResultDataAccessException,统一在适配层包装并保留cause便于追溯,服务层只处理自定义异常实现技术解耦,全局异常处理器映射为HTTP状态码且不向前端泄露敏感信息。

异常包装的核心目标是把底层技术细节(比如数据库驱动异常、HTTP客户端内部错误)转化为业务层能理解、能处理的语义化异常,从而在调用方看不到 SQLException、IOException 或 FeignException 这类框架/基础设施相关的异常类型。
定义清晰的业务异常体系
不直接抛出原始异常,而是创建一组与领域相关的受检或非受检异常类,例如:
-
OrderNotFoundException(代替EmptyResultDataAccessException) -
PaymentProcessingException(代替HttpClientErrorException或TimeoutException) -
InventoryValidationException(代替ConstraintViolationException)
这些异常只暴露“发生了什么业务问题”,不透露“为什么发生”(如用的是 MySQL 还是 PostgreSQL、走的是 RestTemplate 还是 WebClient)。
在适配层统一做包装
把异常转换集中在数据访问层(DAO/Repository)、远程调用封装层(Client)、文件/消息等外部交互入口处。例如:
立即学习“Java免费学习笔记(深入)”;
try {
return jdbcTemplate.queryForObject(sql, rowMapper, orderId);
} catch (EmptyResultDataAccessException e) {
throw new OrderNotFoundException("Order not found: " + orderId, e);
} catch (DataAccessException e) {
throw new DataAccessException("Failed to load order", e);
}
关键点:保留原始异常作为 cause(构造函数传入),便于日志和调试时追溯根因,但对外接口契约中不体现它。
避免在业务逻辑里感知底层异常类型
服务层(Service)应只处理自己定义的异常,不 import org.springframework.dao.* 或 java.net.* 相关异常。例如:
- ✅ 正确:捕获
OrderNotFoundException并返回 404 响应 - ❌ 错误:捕获
SQLException并尝试解析 SQLState 来决定返回码
这样业务逻辑就与持久化技术解耦,换掉 MyBatis 改用 JPA 时,Service 层完全无需修改。
利用 Spring 的 @ExceptionHandler 和 Advice 做统一兜底
在 Controller 层或全局异常处理器中,将业务异常映射为标准 HTTP 状态码和响应体:
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ErrorResponse> handleOrderNotFound(OrderNotFoundException e) {
return ResponseEntity.notFound().build();
}
同时确保原始异常的 stack trace 只记录到日志(含 cause),不返回给前端 —— 防止泄露数据库表名、SQL 片段、中间件版本等敏感信息。
不复杂但容易忽略:包装不是简单套一层 try-catch,而是围绕“谁该知道什么”来设计异常传播路径。底层细节留在最外层日志里,中间各层只传递意图明确的业务信号。


















