Spring Boot使用多个@RestControllerAdvice时的拦截顺序

Spring Boot使用多个@RestControllerAdvice时的拦截顺序

编程文章jaq1232025-10-19 6:01:254A+A-

前言

我们在项目开发中经常会使用到公司的公共模块,例如我的项目book-service,就依赖了公司基础的核心core模块,此时core模块已经集成了@RestControllerAdvice的类,负责拦截所有的controller异常。

但是呢?他的异常处理不符合我们book-service项目的要求,我们需要定制自己的异常处理逻辑。这就导致我们book-service项目要重写自己的controller异常拦截。

@RestControllerAdvice介绍

@RestControllerAdvice 是 Spring Framework 中的一个注解,用于全局处理控制器(@Controller 或 @RestController)中的异常和增强功能。它是 @ControllerAdvice 和 @ResponseBody 的组合注解,主要用于简化 restful API 的异常处理和数据返回。

主要功能

全局异常处理:捕获控制器中抛出的异常,并返回统一的错误响应。

数据绑定增强:对请求参数进行全局处理(如校验、格式化等)。

模型数据增强:向所有控制器方法添加全局的模型数据。

核心特性

异常处理:通过 @ExceptionHandler 注解方法处理特定异常。

数据绑定:使用 @InitBinder 初始化数据绑定器。

模型数据:通过 @ModelAttribute 添加全局模型属性

RestControllerAdvice类定义

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    @AliasFor(
        annotation = ControllerAdvice.class
    )
    String[] value() default {};

    @AliasFor(
        annotation = ControllerAdvice.class
    )
    String[] basePackages() default {};

    @AliasFor(
        annotation = ControllerAdvice.class
    )
    Class<?>[] basePackageClasses() default {};

    @AliasFor(
        annotation = ControllerAdvice.class
    )
    Class<?>[] assignableTypes() default {};

    @AliasFor(
        annotation = ControllerAdvice.class
    )
    Class<? extends Annotation>[] annotations() default {};
}

代码示例

1. 基本用法

import org.springframework.web.bind.annotation.*;
import org.springframework.http.*;

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理特定异常(如资源未找到)
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
        ErrorResponse error = new ErrorResponse("NOT_FOUND", ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    // 处理其他未捕获的异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
        ErrorResponse error = new ErrorResponse("INTERNAL_ERROR", "服务器内部错误");
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

// 自定义错误响应类
class ErrorResponse {
    private String errorCode;
    private String message;

    // 构造方法、Getter和Setter省略
}

2. 结合@ModelAttribute添加全局数据

@RestControllerAdvice
public class GlobalDataHandler {

    @ModelAttribute
    public void addCommonData(Model model) {
        model.addAttribute("appName", "My REST API");
    }
}

3. 结合@InitBinder自定义数据绑定

@RestControllerAdvice
public class CustomBinder {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // 注册自定义编辑器或格式化器
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
}

常见问题

  1. 与 @ControllerAdvice 的区别

@RestControllerAdvice 是 @ControllerAdvice + @ResponseBody 的组合,直接返回 JSON/XML 数据,适合 REST API。

@ControllerAdvice 默认返回视图名称,适合传统 MVC。

  1. 作用范围

默认扫描所有控制器,可通过 basePackages 或 annotations 限定范围:

@RestControllerAdvice(basePackages = "com.example.api")
  1. 优先级

控制器内的方法级异常处理优先于全局处理。

解决异常的方法

1、使用aop进行切面拦截异常

2、controller每个方法都用try-catch捕获异常

3、增加一个@RestControllerAdvice标注的类,负责处理我们项目的controller异常。

我选用第三种方法,但是当我写了个
GlobalExceptionHandlerAdvice类,指定basePackages为我自己的项目包,依旧还是被core模块的全局异常处理类拦截了。

查询相关资料发现如果有多个加了@RestControllerAdvice的类,他们会依次加载,遇到异常时,按照类加载顺序进行判断,如果前面的类有能力处理这个异常的方法,就给前面的类处理。

我的项目中有两个标注了@RestControllerAdvice的类,core模块的类被先加载,且core模块的异常处理类有个方法专门处理Exception类型的异常,所以我定义的GlobalExceptionHandlerAdvice异常处理类始终不执行。

解决方法:

@Order(Ordered.HIGHEST_PRECEDENCE) 使用@Order注解,提高自己的局部异常处理类的加载顺序就行了

/**
 * 自定义全局异常处理
 *
 * @author yangyanping
 * @date 2025-06-17
 */
@Slf4j
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandlerAdvice {
    /**
     * 参数验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidException(HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException e) {
        log.error("handleValidException#Exception,url={}", request.getRequestURI(), e);
        String message = "";
        BindingResult bindingResult = e.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();

        for (FieldError item : fieldErrors) {
            message = item.getDefaultMessage();
            break;
        }

        ErrorResponse error = new ErrorResponse("Param_Valid_Error", "方法参数验证异常:" + message);
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * 参数验证异常
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ErrorResponse> handleConstraintViolationException(HttpServletRequest request, HttpServletResponse response, ConstraintViolationException e) {
        log.error("handleValidException#Exception,url={}", request.getRequestURI(), e);
        String message = "";
        Set<ConstraintViolation<?>> set = e.getConstraintViolations();

        for (ConstraintViolation<?> item : set) {
            message = item.getMessage();
            break;
        }

        ErrorResponse error = new ErrorResponse("Param_Valid_Error", "参数验证异常:" + message);
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

@Order、@Priority、@Primary 三个注解和 Orderd 接口 说明

  • orderd接口,实现Oderd接口的话要实现int getOrder();这个方法,返回一个整数值,值越小优先级越高。
  • @Order里面存储了一个值,默认为Integer的最大值,同样值越小优先级越高。要注意@Order只能控制组件的加载顺序,不能控制注入的优先级。但是能控制List 里面存放的XXX的顺序,原因是当通过构造函数或者方法参数注入进某个List时,Spring的DefaultListableBeanFactory类会在注入时调用AnnotationAwareOrderComparator.sort(listA)帮我们去完成根据@Order或者Ordered接口序值排序。@Order更加适用于集合注入的排序。
  • @Priority与@Order类似,@Order是Spring提供的注解,@Priority是JSR 250标准,同样是值越小优先级越高。但是两者还是有一定区别,@Priority能够控制组件的加载顺序,因此@Priority侧重于单个注入的优先级排序。此外@Priority优先级比@Order更高,两者共存时优先加载@Priority。
  • @Primary是优先级最高的,如果同时有@Primary以及其他几个的话,@Primary注解的Bean会优先加载。

最佳实践

  • 统一错误响应格式:定义标准的错误响应结构(如 ErrorResponse)。
  • 日志记录:在异常处理方法中记录日志,便于排查问题。
  • 精细化异常处理:针对不同异常返回不同的 HTTP 状态码和错误信息。
点击这里复制本文地址 以上内容由jaq123整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

苍茫编程网 © All Rights Reserved.  蜀ICP备2024111239号-21