业务开发中应将受检异常包装为语义清晰的非受检异常,按场景选用合适子类,通过带cause构造器保留根因,在DAO或Service层边界封装,并配套日志与全局异常处理器提升可观测性。

在业务开发中,把受检异常包装成非受检异常,不是为了绕过编译检查,而是让异常更贴合业务语义、减少调用方干扰、便于统一治理。
选对运行时异常类型
避免直接用 RuntimeException(e)。应按错误场景选用语义清晰的子类:
- 参数解析失败(如 JSON 反序列化出错)→ IllegalArgumentException 或其子类
- 外部服务不可达或超时 → 自定义 ExternalServiceException,带服务名、超时值等字段
- 状态冲突(如已发货订单再尝试发货)→ IllegalStateException 或定制子类
- 数据持久层问题(如 SQL 执行失败)→ Spring 的 DataAccessException,或自定义 PersistenceException
保留原始异常上下文
包装必须通过带 Throwable cause 参数的构造函数完成,否则根因丢失,排查困难:
- ✅ 正确写法:throw new BusinessException("配置加载失败", e);
- ❌ 错误写法:throw new BusinessException("配置加载失败");(e 被丢弃)
- 自定义异常类需显式提供 public XxxException(String msg, Throwable cause) 构造器
在边界层完成封装
包装动作应发生在技术异常首次暴露给业务逻辑的位置,避免污染上层:
- DAO 层捕获 SQLException、IOException,转为业务可理解的运行时异常
- Service 层调用 HTTP 客户端时,将 RestClientException 或 IOException 按语义包装
- 不要在 Controller 层才做包装——此时异常已穿透多层,违背分层原则
- 禁止在工具方法内部静默吞掉异常,也不该把原始受检异常重新 throw 出去
配套可观测性机制
包装后异常虽不强制处理,但可观测性不能打折:
- 在 catch 块中记录 WARN 或 ERROR 日志,包含方法名、关键入参、原始异常类名和消息
- 避免重复打印:不建议 log 后又 throw 新异常,除非补充了关键上下文;优先由全局异常处理器输出完整堆栈
- 使用 Spring 时,配合 @ControllerAdvice 统一捕获所有业务异常,返回结构化错误响应
- 若涉及异步或函数式编程(如 Stream、CompletableFuture),用 UncheckedIOException 等标准包装类,或封装适配器方法桥接

















