统一拦截、分层捕获、结构响应是异常处理核心;@RestControllerAdvice仅捕获Controller层抛出的异常,需避免Service吞异常;应构建BusinessException(4xx)、SystemException(5xx)等分层体系,并按继承顺序注册处理器;响应体含code/msg/data/timestamp,日志通过MDC注入traceId实现链路追踪。

核心就三点:统一拦截、分层捕获、结构响应。不靠堆砌注解,而靠设计逻辑。
掌握 @RestControllerAdvice 的作用边界
它只拦截 Controller 层抛出的异常,不处理 Filter、Interceptor、Service 或异步线程里的异常。必须确保异常真正“冒泡”到 Controller 才能被它捕获。常见误区是 Service 中吞掉异常(比如 try-catch 后 return null),导致全局处理器完全失效。
建议做法:
- Controller 方法体里不写 try-catch,让业务异常自然向上抛
- 用 @Valid 校验参数时,配合 @ExceptionHandler(MethodArgumentNotValidException.class) 单独处理
- 数据库异常(如 SQLSyntaxErrorException)通常属于系统异常,应归入兜底处理器,而非业务逻辑层直接 catch
构建分层异常体系,别只用 Exception
把异常按语义和 HTTP 状态码分类,而不是全扔给一个 @ExceptionHandler(Exception.class)。例如:
- BusinessException:4xx 类错误(参数错、权限不足、资源不存在),前端可读提示
- SystemException:5xx 类错误(DB 连接失败、远程调用超时),统一返回“系统繁忙”,不暴露细节
- IllegalArgumentException / NullPointerException:开发期问题,日志中记录堆栈,生产环境建议转为 400 或 500 并屏蔽敏感字段
关键点:子类异常处理器优先于父类,所以 BusinessException 的 handler 必须写在 Exception 的 handler 之前,否则会被后者提前截走。
统一响应体 + 日志上下文不能少
返回格式要前后端对齐,比如固定字段 code(业务码)、msg、data、timestamp;同时日志需带 traceId 关联请求链路。
实操要点:
- 用 MDC.put("traceId", UUID.randomUUID().toString()) 在拦截器或 Filter 中注入 traceId
- 在异常处理器里通过 MDC.get("traceId") 取出并写入日志,便于排查
- 系统异常响应中 code 固定为 "SYS_ERROR",前端据此做降级或重试,不依赖 HTTP 状态码判断逻辑
不复杂但容易忽略。


















