Skip to content

全局异常处理

作用

@RestControllerAdvice 是 Spring MVC 提供的全局异常处理注解,本质是 @ControllerAdvice@ResponseBody 的组合,具备以下能力:

  1. 全局捕获控制器(@Controller/@RestController)抛出的异常,统一返回格式

  2. 可通过属性限定作用范围,实现精细化异常处理

  3. 支持与 @ExceptionHandler 配合,针对性处理不同异常类型

作用范围配置与触发条件

作用范围配置方式

通过注解的 basePackages(或 value,两者等效)属性指定作用的控制器包路径,格式如下:

java
// 单个包配置
@RestControllerAdvice(basePackages = "com.example.a-controller")

// 多个包配置
@RestControllerAdvice(basePackages = {"com.example.a-controller", "com.example.c-controller"})

异常触发的核心条件

只有满足以下条件,注解标记的处理类才会生效:

  • 异常的最终抛出者是 basePackages 范围内的控制器(即异常从指定包内的控制器抛出)

  • 异常类型与处理类中 @ExceptionHandler 声明的类型匹配(直接匹配或子类匹配)

典型场景说明

指定包内控制器抛异常

  • 配置:@RestControllerAdvice(basePackages = "a-controller")

  • 异常来源:a-controller 包下的 UserController 抛出 RuntimeException

  • 结果:处理类触发,执行对应的 @ExceptionHandler(RuntimeException.class) 方法

指定包外控制器抛异常

  • 配置:同上

  • 异常来源:b-controller 包下的 OrderController 抛出 RuntimeException

  • 结果:处理类不触发(作用范围不包含 b-controller)

Service 层抛异常,控制器未捕获

  • 异常链路:a-controller 的 UserController → 调用 UserService → Service 抛 NullPointerException(未捕获)

  • 结果:异常最终抛到 UserController,处理类触发(满足 “源头控制器在范围内” 条件)

异常处理优先级规则

当存在多个 @RestControllerAdvice 处理类时,触发顺序遵循以下优先级规则(从高到低):

作用范围越具体,优先级越高

  • 包级处理类(指定 basePackages)> 全局处理类(无 basePackages 配置)

  • 示例:

  • 处理类 A(包级):@RestControllerAdvice(basePackages = "a-controller")

  • 处理类 B(全局):@RestControllerAdvice

  • 当 a-controller 抛异常时,A 优先触发;其他包抛异常时,B 触发

同一作用范围下,异常类型越具体,优先级越高

同一处理类或同作用范围的多个处理类中,@ExceptionHandler 声明的异常类型越具体,越优先匹配:

java
// 处理类(作用于 a-controller 包)
@RestControllerAdvice(basePackages = "a-controller")
public class ExceptionAdvice {
    // 优先级高:具体异常
    @ExceptionHandler(NullPointerException.class)
    public Result handleNPE(NullPointerException e) { ... }

    // 优先级低:宽泛异常(父类)
    @ExceptionHandler(Exception.class)
    public Result handleAll(Exception e) { ... }
}
  • 当 a-controller 抛 NullPointerException 时,handleNPE 优先触发,而非 handleAll

同作用范围 + 同异常类型,Order 优先级决定

当两个处理类满足:

  • 作用范围完全相同(同一组包)

  • 均包含处理同一异常类型的方法

此时通过 @Order 注解或 Ordered 接口指定优先级(值越小,优先级越高):

示例实现

java
// 处理类A:优先级1(更高)
@RestControllerAdvice(basePackages = "a-controller")
@Order(1)
public class AdviceA {
    @ExceptionHandler(MyBusinessException.class)
    public Result handleA(MyBusinessException e) { ... }
}

// 处理类B:优先级2(更低)
@RestControllerAdvice(basePackages = "a-controller")
@Order(2)
public class AdviceB {
    @ExceptionHandler(MyBusinessException.class)
    public Result handleB(MyBusinessException e) { ... }
}

当 a-controller 抛 MyBusinessException 时,AdviceA 的 handleA 优先触发,AdviceB 不执行

注意事项:未指定 Order 且未实现 Ordered 时,触发顺序由 Spring 类扫描顺序决定(不确定,不推荐)

常见问题与解决方案

配置 basePackages 后,异常未触发处理类

  • 排查点 1:异常是否最终抛到 basePackages 范围内的控制器

  • 排查点 2:@ExceptionHandler 声明的异常类型是否与抛出类型匹配(注意:子类异常可匹配父类处理器,但父类异常不能匹配子类处理器)

  • 排查点 3:是否存在优先级更高的处理类已拦截该异常

同一异常被多个处理类触发

  • 原因:多个处理类作用范围包含异常来源控制器,且未明确优先级

  • 解决方案:通过 @Order 明确处理类优先级,或缩小部分处理类的作用范围

全局处理类与包级处理类冲突

  • 原则:包级处理类负责处理本包内的特定异常,全局处理类负责兜底(如未被包级处理的异常)

  • 建议:全局处理类仅保留 Exception 级别的兜底处理,避免与包级处理类重复