异常处理:@ControllerAdvice 与 HandlerExceptionResolver

结论先行:Spring MVC 的统一异常出口是 DispatcherServlet 中的 HandlerExceptionResolver 责任链,默认注册三个:ExceptionHandlerExceptionResolver(识别 @ExceptionHandler,最常用)、ResponseStatusExceptionResolver(识别 @ResponseStatus)、DefaultHandlerExceptionResolver(处理 404/405/参数类型错误等内建 MVC 异常)。工程上"全局统一异常"的标准做法:@RestControllerAdvice + @ExceptionHandler,把业务异常、参数校验异常与兜底异常统一转成统一响应体。

一、三个默认解析器

解析器依据说明
ExceptionHandlerExceptionResolver@ExceptionHandler 注解局部(@Controller 内)与全局(@ControllerAdvice);按异常类型/继承匹配
ResponseStatusExceptionResolver@ResponseStatus把异常映射为 HTTP 状态码
DefaultHandlerExceptionResolverSpring MVC 内建异常NoHandlerFoundException→404、方法不支持→405、类型转换失败→400 等

二、全局异常处理器代码

@RestControllerAdvice
public class GlobalExceptionHandler {
    // 业务异常:返回业务码
    @ExceptionHandler(BizException.class)
    public Result<Void> biz(BizException e) { return Result.fail(e.getCode(), e.getMessage()); }

    // 参数校验异常(@RequestBody 触发 MethodArgumentNotValidException)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> valid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors().stream()
                .map(f -> f.getField() + ": " + f.getDefaultMessage())
                .collect(Collectors.joining("; "));
        return Result.fail(400, msg);
    }

    // 兜底异常:记录日志,防止堆栈泄漏给前端
    @ExceptionHandler(Exception.class)
    public Result<Void> other(Exception e) {
        log.error("unhandled exception", e);
        return Result.fail(500, "系统繁忙,请稍后再试");
    }
}

三、查找顺序与优先级

方法内 try-catch → 所在 @Controller 的 @ExceptionHandler → 全局 @ControllerAdvice → 内建解析器 → 容器错误页/默认 500。多个匹配时取"异常类型最精确"的处理器。

常见追问 / 记忆点

  • 追问 1:@ControllerAdvice 与 @RestControllerAdvice 区别?答:后者 = 前者 + @ResponseBody 语义,方法返回值直接序列化 JSON,纯接口项目用它。
  • 追问 2:怎么返回真正的 404/400 状态码?答:抛 ResponseStatusException(HttpStatus.NOT_FOUND, ...) 或异常类标 @ResponseStatus;注意 404 默认需开启 throw-exception-if-no-handler-found 类配置。
  • 追问 3:多个 @ExceptionHandler 重叠怎么选?答:按异常继承关系取最近的匹配(子类异常优先于父类)。
  • 记忆点:"注解异常找 ExceptionHandler 注解方法,全局用 ControllerAdvice,内建异常有兜底解析器;最外层永远要留 Exception 兜底"。
笔记加载中…