@ControllerAdvice 实现异常“分层分发”,按类型精准拦截:业务异常返400、系统异常返500并记堆栈、校验异常返结构化信息、HTTP语义异常直映状态码;支持包/注解限定作用域;统一用@ResponseStatus和Result封装响应;仅拦截Controller层透出的异常。

用 @ControllerAdvice 实现异常处理的“分层分发”,核心是让异常按类型、按责任自然下沉到最合适的处理位置,而不是在每个 Controller 里硬塞 try-catch。它不是把所有异常都塞进一个兜底方法,而是建立一套有优先级、有分工的响应机制。
按异常类型精准拦截
不同异常代表不同问题层级,应由不同 @ExceptionHandler 方法承接:
- 业务异常(如 OrderNotExistException):返回 400 状态码 + 可读提示,不打堆栈日志,前端可直接展示
- 系统异常(如 NullPointerException、SQLException):记录完整堆栈,返回 500 + 通用提示,避免暴露敏感信息
- 参数校验异常(如 MethodArgumentNotValidException):提取 BindingResult 中的错误字段,返回结构化校验失败信息
- HTTP 状态语义明确的异常(如 HttpRequestMethodNotSupportedException):直接映射为对应状态码,无需额外包装
用 basePackages 或 annotations 限定作用范围
@ControllerAdvice 默认作用于全部 @Controller,但实际项目中常需隔离处理逻辑:
- 给管理后台和用户前台分别定义两个 @ControllerAdvice 类,避免错误提示风格混用
- 用 @ControllerAdvice(annotations = RestController.class) 只增强 REST 接口,不影响页面跳转类控制器
- 用 @ControllerAdvice(basePackages = "com.example.api") 将异常处理约束在 API 模块内,防止干扰内部服务调用链
配合 @ResponseStatus 和统一响应体保持一致性
每个 @ExceptionHandler 方法应明确 HTTP 状态码,并封装为项目约定的响应格式(如 Result<T>):
- 不依赖方法内手动设置 response.setStatus(),而是用 @ResponseStatus(code = HttpStatus.BAD_REQUEST) 声明语义
- 返回值统一为 ResponseEntity<Result<?>> 或直接 Result<?>(配合 @ResponseBody)
- 在兜底方法中加入 requestId,便于日志追踪,例如:log.error("unhandled exception [{}]", requestId, e);
注意拦截边界:只管 Controller 层抛出的异常
@ControllerAdvice + @ExceptionHandler 仅捕获从 Controller 方法体中向上抛出的异常,以下情况不会被拦截:
- Filter、Interceptor 中抛出的异常
- 异步线程(@Async)中发生的异常
- 定时任务(@Scheduled)中的异常
- Controller 内部已用 try-catch 吞掉的异常(必须让异常透出才能被拦截)

















