java受检异常应统一包装为runtimeexception,以明确分层架构中各层职责:dao层转为带上下文的dataaccessexception,服务层按语义封装业务或外部异常,接口层由@controlleradvice统一响应;包装须保留cause链、附业务标识、禁用suppressed,自定义异常需轻量、语义清晰,并配套日志、监控与差异化返回机制。

Java受检异常统一包装为RuntimeException,不是为了绕过编译检查,而是为了在分层架构中明确职责边界:底层基础设施暴露原始风险,上层业务专注规则表达,网关层统一反馈。关键不在“包不包”,而在“怎么包、包完怎么用”。
分层封装:每层只处理该管的异常
异常不该穿透整个调用链。各层应按语义做一次归一化转换:
- DAO/数据访问层:捕获SQLException、JDBCException等,转为带操作上下文的运行时异常(如DataAccessException),附带SQL片段(脱敏后)、执行耗时、表名;不抛出任何受检异常
- 服务层(Service):对校验失败(如参数非法、状态冲突)直接抛业务运行时异常(如InvalidOrderStatusException);对外部调用失败(HTTP、RPC)封装为ExternalServiceException,并携带服务标识、超时配置、重试次数
- 接口层(Controller/API):不主动throw任何异常,仅声明可能抛出的业务异常类型;所有异常最终由@ControllerAdvice统一拦截并转为标准响应体
包装必须带上下文和cause,不能只写new RuntimeException(e)
空泛包装等于丢弃诊断信息。每次包装都要回答三个问题:谁出的问题?在哪出的?为什么出的?
- 消息中包含业务标识:例如"Failed to process payment for order #ORD-20260607-8892",而非"Payment failed"
- 构造时必须传入原始异常:
new PaymentProcessingException(msg, e),确保e.getCause()可逐层追溯 - 禁用
addSuppressed()代替cause——suppressed用于资源关闭失败等辅助异常,主失败路径只能走cause链
自定义异常要轻量、可识别、易扩展
异常类不是数据载体,是语义信标。设计时守住三条线:
- 继承
RuntimeException,避免强制调用方写try-catch;若确需调用方强制处理(极少数金融强契约场景),才继承Exception - 提供且仅提供两个构造器:一个
String message,一个String message, Throwable cause;其他字段(如errorCode、context map)可有,但不参与异常链传递 - 类名直指业务含义:
InsufficientStockException比BusinessException更有效;避免泛化命名如BaseException或AppException
配套机制缺一不可:日志、监控、返回体
把受检异常转成运行时异常后,如果没配套,就等于把错误藏得更深了。
- 日志框架(如Logback)需配置打印完整的cause链,不能只输出第一层异常类名
- 全局异常处理器中,对不同子类做差异化响应:业务异常返回
400 Bad Request+ 错误码;系统级异常(如NPE)返回500 Internal Error,且不返回堆栈给前端 - 监控系统(如SkyWalking、Prometheus)应采集异常类型、发生频次、关联traceId,而不是只统计
RuntimeException总数
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











