java多层架构中统一处理受检异常的关键是将其包装为自定义运行时业务异常并在表现层集中转化响应。需定义继承runtimeexception的basebusinessexception基类,dao/service层主动将sqlexception等转为语义化子类,再通过@controlleradvice+@exceptionhandler统一拦截并返回标准化json,同时补充线程级兜底日志。

受检异常(Checked Exception)在 Java 多层架构中必须显式处理,否则编译不通过。但它本身不便于统一拦截——因为 Controller 层无法直接捕获业务层或数据层抛出的受检异常,除非层层 throws 或用 try-catch 吞掉再转成运行时异常。真正的统一处理,关键不在“绕过编译检查”,而在于**把受检异常纳入统一异常体系,并在表现层集中转化和响应**。
统一定义业务异常基类(继承 RuntimeException)
不建议让 Controller 直接暴露 SQLException、IOException 等原始受检异常。应将它们在最外层(如 Service 或 DAO 调用处)包装为自定义的运行时业务异常:
- 定义
BaseBusinessException继承RuntimeException,带errorCode、message、httpStatus字段 - DAO 层遇到
SQLException时,不向上 throws,而是 catch 后转为DataAccessException(继承自BaseBusinessException) - Service 层校验失败时,抛
ParamValidationException;资源不存在时抛ResourceNotFoundException - 所有子类都保持“无需强制 try-catch”,但语义清晰、可被全局处理器识别
@ControllerAdvice + @ExceptionHandler 拦截所有业务异常
Spring 的 @ControllerAdvice 是表现层统一兜底的核心机制,它能捕获 Controller 方法执行中抛出的任意异常(包括你包装后的运行时业务异常):
- 用
@ExceptionHandler(BaseBusinessException.class)捕获全部业务异常,返回标准化 JSON(如{"code":1002,"msg":"参数格式错误"}) - 单独配置
@ExceptionHandler(MethodArgumentNotValidException.class)处理 JSR-303 校验异常 - 最后用
@ExceptionHandler(Exception.class)捕获未预期异常(记录日志 + 返回 500),避免裸露堆栈 - 注意:该机制只对进入 DispatcherServlet 的请求生效,不覆盖 Filter 或异步线程中的异常
DAO/Service 层主动转换受检异常
这是让统一处理“真正落地”的关键动作——不能依赖框架自动处理受检异常,必须人工干预转化点:
- MyBatis 中,
SqlSession抛出的PersistenceException是运行时异常,但底层仍是SQLException;可在自定义Interceptor或DataSource包装器中统一转换 - JDBC 直连场景下,在 DAO 方法内
catch(SQLException),根据 SQLState 或 ErrorCode 映射为具体业务异常(如 “23505” →DuplicateKeyException) - 调用第三方 HTTP SDK(如 Apache HttpClient)时,把
IOException、ParseException统一封装为ExternalServiceException - 转换时不丢失原始异常(作为 cause 传入),确保日志和监控能追溯根因
补充兜底:全局未捕获异常处理器
即使做了以上三层,仍有漏网之鱼(如定时任务线程、@Async 方法、Filter 初始化阶段):
- 设置
Thread.setDefaultUncaughtExceptionHandler,捕获非主线程未处理异常 - Spring 提供
TaskExecutionProperties配置异步线程池的异常处理器 - 主程序启动类中注册
SpringApplication.setUncaughtExceptionHandler,处理启动失败 - 所有兜底日志必须包含 traceId、请求路径、时间戳,便于链路排查











