private Method getMappedMethod(Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList<>(); //遍历该解析器下对应的异常处理方法的key,我们测试类的应该是有三个方法,对应着Exception,RuntimeException,ArithmeticException三个异常
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { //判断当前遍历的异常是否等于传入的异常,或者为他的父类或超类,很显然,我们遍历的这三个都符合
if (mappedException.isAssignableFrom(exceptionType)) { //符合就添加进列表中
matches.add(mappedException);
} //如果列表不为空,说明有符合条件的对应方法
if (!matches.isEmpty()) { //符合条件的方法大于1个,就对列表进行排序,这里就是确定当存在多个能够处理同一异常的方法如何选择
if (matches.size() > 1) { //来重点看下这个自定义排序器的内部排序规则
matches.sort(new ExceptionDepthComparator(exceptionType));
} //返回排序完列表的第一个元素,也就是深度最低的一个,跟传入异常关系最近的一个,可以理解为就近原则。
return this.mappedMethods.get(matches.get(0));
else {
return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
ExceptionDepthComparator的排序规则,可以看到就是计算每个异常的深度,这个深度就是和传入的异常进行比较,如何是同一个异常深度就是0,不等就深度+1然后继续用该异常的父类判断,不断递归,把列表中所有异常相对于传入异常的深度都计算完,然后升序排序。
public int compare(Class<? extends Throwable> o1, Class<? extends Throwable> o2) {
int depth1 = getDepth(o1, this.targetException, 0);
int depth2 = getDepth(o2, this.targetException, 0);
return (depth1 - depth2);
private int getDepth(Class<?> declaredException, Class<?> exceptionToMatch, int depth) {
if (exceptionToMatch.equals(declaredException)) {
// Found it!
return depth;
// If we've gone as far as we can go and haven't found it...
if (exceptionToMatch == Throwable.class) {
return Integer.MAX_VALUE;
return getDepth(declaredException, exceptionToMatch.getSuperclass(), depth + 1);
到这里我们就看完了@ControllerAdvice+@ExceptionHandler执行流程的核心代码,完整流程的源码较多,本文没有全部展示,但是这些核心部分应该可以让我们了解SpringMVC是如何在抛出异常后,找到合适异常处理器进行处理的。
4. 拓展
在日常开发中,可能有需要到某个类的异常要这么处理,另外个类的异常要那么处理,这就相当于需要一些定制化处理,这个时候我们系统中只有一个全局异常处理器就难以满足需求,这时候我们可以定制多个全局异常处理器,在@ControllerAdvice属性上可以指定覆盖的范围,如下示例
我们在程序中定义了两个异常处理器,其中Yuqi2ExceptionHandler在@ControllerAdvice注解上的assignableTypes属性上指定了TestController,这样的话当TestController中抛出算术异常时,就会走Yuqi2ExceptionHandler的处理。这样就实现了对特殊类的定制化处理
@RestControllerAdvice
public class YuqiExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e) {
return "handle ArithmeticException";
@RestControllerAdvice(assignableTypes = {TestController.class})
public class Yuqi2ExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e) {
return "handle ArithmeticException v2";
那当我们有两个定制异常处理类都对定制类有特殊处理,这个时候SpringMVC会怎么选择呢,如下示例,两个异常处理器都都指定了生效范围为TestController,这个时候TestController触发异常时,经测试都是走Yuqi2这个处理器,debug源码看到,因为在遍历ControllerAdviceBean时,Yuqi2在Yuqi3的位置上面,所以遍历的时候直接就在Yuqi2结束处理了,我们可以在处理器中手动加@Order注解,指定它们的加载属性,例如Yuqi2上加@Order(2),Yuqi3上加@Order(1),这样Yuqi3的加载顺序就优先,再次触发异常时,就会走Yuqi3的处理。
@RestControllerAdvice(assignableTypes = {TestController.class})
public class Yuqi2ExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e) {
return "handle ArithmeticException v2";
@RestControllerAdvice(assignableTypes = {TestController.class})
public class Yuqi3ExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e) {
return "handle ArithmeticException v3";
5. 最后
相信跟着流程看了一遍源码后,大家对@ControllerAdvice和@ExceptionHandler的执行原理有了更加清晰的认识,通过源码重新过了一遍同时也是我自己学习了一遍,由于笔者水平有限,文中可能有一些不对的地方,希望大家能够指出,共同进步。
原文:从源码看全局异常处理器@ControllerAdvice&@ExceptionHandler的生效原理 - Yuqi与其 - 博客园