全局异常处理
作用
@RestControllerAdvice 是 Spring MVC 提供的全局异常处理注解,本质是 @ControllerAdvice 与 @ResponseBody 的组合,具备以下能力:
全局捕获控制器(
@Controller/@RestController)抛出的异常,统一返回格式可通过属性限定作用范围,实现精细化异常处理
支持与
@ExceptionHandler配合,针对性处理不同异常类型
作用范围配置与触发条件
作用范围配置方式
通过注解的 basePackages(或 value,两者等效)属性指定作用的控制器包路径,格式如下:
// 单个包配置
@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 声明的异常类型越具体,越优先匹配:
// 处理类(作用于 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 接口指定优先级(值越小,优先级越高):
示例实现
// 处理类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 级别的兜底处理,避免与包级处理类重复
