业务开发中应将受检异常用语义明确的RuntimeException子类(如IllegalArgumentException、IllegalStateException)或自定义BusinessException封装,通过带cause构造函数保留原始异常,于DAO/Service边界层完成包装,并配合日志与全局异常处理器保障可观测性。

在业务开发中,将受检异常(checked exception)包装为非受检异常(unchecked exception)向上抛出,核心是用 RuntimeException 或其子类作为“载体”进行封装,避免强制调用方处理或声明异常,同时保留原始异常的上下文和堆栈信息。
选择合适的运行时异常类型
不建议直接 new RuntimeException(e) 了事。应根据语义选用更明确的子类,或自定义业务异常:
- 用
IllegalArgumentException包装参数校验失败引发的受检异常(如解析 JSON 失败) - 用
IllegalStateException包装因状态不一致导致的受检异常(如文件已关闭却尝试读取) - 定义统一的业务异常基类(如
BusinessException extends RuntimeException),便于全局捕获和统一处理 - 若需区分异常来源,可添加错误码、模块标识等字段,构造时传入原始异常作为 cause
正确传递原始异常(cause)
必须通过带 Throwable cause 参数的构造函数完成包装,否则会丢失根因信息,给排查带来困难:
- ✅ 正确:
throw new BusinessException("读取配置失败", e); - ❌ 错误:
throw new BusinessException("读取配置失败");(e 被丢弃) - 确保自定义异常构造器支持 cause 传递,例如:
public BusinessException(String message, Throwable cause) {<br> super(message, cause);<br>}
在关键位置做封装,避免污染上层逻辑
包装动作应发生在“边界层”,即受检异常首次暴露给业务逻辑的地方:
- DAO 层调用 JDBC/MyBatis 时捕获
SQLException,转为DataAccessException(Spring 已提供)或自定义异常 - Service 层调用 HTTP 客户端(如 OkHttp、RestTemplate)时捕获
IOException、InterruptedException,按业务含义包装 - 不要在 Controller 层才包装——此时异常已穿透多层,违背封装原则;也不要在工具类内部静默吞掉异常
配合日志与监控,确保可观测性
包装后异常虽不再强制处理,但不能降低可观测性:
- 在包装前记录 WARN 或 ERROR 日志,输出关键上下文(如方法名、参数摘要、原始异常类名)
- 避免重复打印:不建议在 catch 块里 log 后又 throw 新异常,除非需要补充信息;优先让统一异常处理器记录完整堆栈
- 若使用 Spring,可通过
@ControllerAdvice统一处理所有RuntimeException子类,返回友好提示并上报错误中心

















