题目
设计多层服务调用中的异常处理框架
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
异常封装与转换,异常链维护,资源清理安全,上下文信息传递
快速回答
核心解决方案要点:
- 创建自定义异常基类,封装错误码和上下文元数据
- 使用异常链(
initCause()/带Throwable参数的构造器)保持原始异常 - 结合
try-with-resources和finally块确保资源释放 - 在服务边界进行异常转换(如DAO→Service→Controller层)
- 使用线程局部变量(ThreadLocal)传递调用上下文
问题场景
在分布式系统中,一个业务请求可能跨越DAO层、服务层、RPC层等多个调用层级。需要设计异常处理框架确保:
- 底层异常信息不丢失
- 敏感信息(如SQL语句)不暴露给上层
- 资源(数据库连接、文件句柄)100%释放
- 携带业务上下文(如订单ID、用户ID)
核心实现方案
1. 自定义异常体系
// 基础业务异常(含错误码和上下文)
public abstract class BusinessException extends RuntimeException {
private final String errorCode;
private final Map<String, String> context = new HashMap<>();
public BusinessException(String message, String errorCode, Throwable cause) {
super(message, cause); // 关键:保存原始异常
this.errorCode = errorCode;
}
public void addContext(String key, String value) {
context.put(key, value);
}
// 获取完整上下文信息(JSON格式)
public String getContextInfo() { /*...*/ }
}
// 分层子类示例(DAO层)
public class DaoException extends BusinessException {
public DaoException(String message, Throwable cause) {
super(message, "ERR_DAO_001", cause);
}
}
2. 异常转换与链式传递
// Service层处理DAO异常
public Order getOrder(String orderId) throws ServiceException {
try (Connection conn = dataSource.getConnection()) { // try-with-resources
OrderDAO dao = new OrderDAO(conn);
return dao.fetchOrder(orderId);
} catch (DaoException ex) {
// 添加业务上下文
ex.addContext("orderId", orderId);
// 转换为服务层异常(保留原始异常)
throw new ServiceException("订单查询失败", "ERR_SVC_200", ex);
} finally {
// 清理线程局部变量(如事务ID)
TransactionContext.clear();
}
}
最佳实践
- 分层转换:每层封装专属异常(DAOException → ServiceException → ApiException)
- 上下文传递:使用
ThreadLocal存储请求级数据(如MDC) - 资源释放:
- 优先使用
try-with-resources(实现AutoCloseable) - 复杂资源在
finally中显式释放
- 优先使用
- 日志记录:在顶层统一记录异常(包含完整上下文)
常见错误
| 错误示例 | 后果 | 修正方案 |
|---|---|---|
catch(Exception e) { throw new MyException("error"); } | 原始异常丢失 | 传入原始异常:throw new MyException("error", e) |
catch(SQLException e) { throw new ServiceException(e.getMessage()); } | 堆栈信息截断 | 直接传递异常对象 |
在finally中抛出异常 | 覆盖主逻辑异常 | 使用嵌套try-catch处理清理异常 |
扩展知识
- 响应式编程异常处理:Project Reactor中的
onErrorResume()/onErrorMap() - JVM限制:
- 异常
cause链最多支持250层(可配置) - 频繁抛出异常影响性能(创建栈快照)
- 异常
- 框架集成:Spring的
@ControllerAdvice统一异常处理