• 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;
      }
    }
    
  • 相关阅读:
    HDU--2546 饭卡(01背包)
    UVA--562 Dividing coins(01背包)
    UVA--624 CD(01背包+路径输出)
    PKU--3628 Bookshelf 2(01背包)
    ExecutorService介绍2
    ExecutorService介绍
    mac下设置命令别名
    如何在sourcetree 下提交代码到gerrit上
    vim下如何删除某行之后的所有行
    VMware网络设置的三种方式
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/SpringMvc_ExceptionHandlerMethodResolver.html
Copyright © 2020-2023  润新知