SpringMVC之从ExceptionHandlerMethodResolver源码解析与@ExceptionHandler的使用注意点

源码分析

一、构造函数

org.springframework.web.method.annotation.ExceptionHandlerMethodResolver 仅有一个构造函数,源码如下:

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
  // 找到当前 handler 类中带有 @ExceptionHandler 注解的方法,并循环遍历
  for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
    // 提取方法注解或者方法参数中的异常
    for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
      // 设置异常和对应方法之间的映射关系
      addExceptionMapping(exceptionType, method);
    }
  }
}

方法参数 handlerType 通常是被 @Controller 注解的类,或者被 @ControllerAdvice 注解的类。

1.1 detectExceptionMappings

private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
  List<Class<? extends Throwable>> result = new ArrayList<>();
  // 首先,尝试提取 @ExceptionHandler 注解中 value 数组
  detectAnnotationExceptionMappings(method, result);
  // 如果注解中 value 数组为空,进入 if
  if (result.isEmpty()) {
    // 遍历方法的参数
    for (Class<?> paramType : method.getParameterTypes()) {
      // 假如有参数是 Throwable 的子类,即 Error 或者 Exception 的子类,加入结果列表中
      if (Throwable.class.isAssignableFrom(paramType)) {
        result.add((Class<? extends Throwable>) paramType);
      }
    }
  }
  // 如果从注解 value 或者是方法参数中都没有找到异常类,进入 if 并抛出异常
  if (result.isEmpty()) {
    throw new IllegalStateException("No exception types mapped to " + method);
  }
  return result;
}

通过这段源码,我们知道了两种指定异常的方式。
第一种是在注解 value 中指定:

@ExceptionHandler(IllegalArgumentException.class)
public String handleIllegalArgument() {
  return "ILLEGAL_ARGUMENT";
}

第二种是在方法参数中指定:

@ExceptionHandler
public String handleIllegalState(IllegalStateException ex) {
  return "ILLEGAL_STATE";
}

1.2 addExceptionMapping

addExceptionMapping 源码比较简单,如下图所示:

由构造函数可知,通常一个 Class 对应一个 ExceptionHandlerMethodResolver
如果一个类中有两个带有 @ExceptionHandler 方法,处理的是同一类异常,就会抛出 IllegalStateException
例如,以下代码就会报错:

import com.fasterxml.jackson.core.JsonParseException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 功能描述: 全局异常处理器
 *
 * @author 20024968@cnsuning.com
 * @version 1.0.0
 */
@RestControllerAdvice
public class GlobalExceptionAdvice {

  @ExceptionHandler(JsonParseException.class)
  public String requestBodyCauseException() {
    return "REQUEST_JSON_SYNTAX_ERROR";
  }

  @ExceptionHandler
  public String requestBodyCauseException(JsonParseException e) {
    return e.getMessage();
  }
}

二、通过异常找方法

通过异常找方法的调用,在 org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver 中。

2.1 resolveMethodByThrowable

public Method resolveMethodByThrowable(Throwable exception) {
  Method method = resolveMethodByExceptionType(exception.getClass());
  if (method == null) {
    Throwable cause = exception.getCause();
    if (cause != null) {
      // 这里有一个递归调用
      method = resolveMethodByThrowable(cause);
    }
  }
  return method;
}

如果,当前 异常类 调用 resolveMethodByExceptionType 没有得到一个 Method 时,那就 getCause() 获得下一层异常类,再去寻找对应的 Method,递归结束的条件:

  • 找到一个 Method 对应某一层 异常;
  • 异常所有上层异常都被找遍了,仍然没有找到对应的 Method

2.2 resolveMethodByExceptionType

public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
  Method method = this.exceptionLookupCache.get(exceptionType);
  if (method == null) {
    method = getMappedMethod(exceptionType);
    this.exceptionLookupCache.put(exceptionType, method);
  }
  return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}

resolveMethodByExceptionType 这个方法,主要是把 异常类 对应的 Method 缓存到 exceptionLookupCache 映射中。

2.3 getMappedMethod

private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
  List<Class<? extends Throwable>> matches = new ArrayList<>();
  // mappedMethods 是在 addExceptionMapping 时添加的,就调用构造函数时初始化的
  for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
    // 当前判断为真的情况:
    // 此mappedException对象表示的类或接口与exceptionType参数表示的类或接口相同,
    // 或者mappedException对象表示的类或接口是exceptionType的超类或超接口。
    if (mappedException.isAssignableFrom(exceptionType)) {
      matches.add(mappedException);
    }
  }
  if (!matches.isEmpty()) {
    // 深度排序,选择与exceptionType最接近的一个mappedException
    if (matches.size() > 1) {  
      matches.sort(new ExceptionDepthComparator(exceptionType));
    }
    return this.mappedMethods.get(matches.get(0));
  }
  else {
    // 找不到异常类匹配的方法时,会返回当前类中的 noMatchingExceptionHandler 方法
    return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
  }
}
posted @ 2022-03-16 22:54  极客子羽  阅读(688)  评论(0编辑  收藏  举报