@ExceptionHandler原理淺析

初始化

初始化 ExceptionHandlerExceptionResolver#exceptionHandlerAdviceCache
初始化方法:initExceptionHandlerAdviceCache()

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
        return Arrays.stream(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class))
                .filter(name -> !ScopedProxyUtils.isScopedTarget(name))
                .filter(name -> context.findAnnotationOnBean(name, ControllerAdvice.class) != null)
                .map(name -> new ControllerAdviceBean(name, context))
                .collect(Collectors.toList());
    }

advice排序

對獲取的bean排序:AnnotationAwareOrderComparator#sort(adviceBeans)

尋找異常處理方法

遍歷ControllerAdviceBean,尋找被@ExceptionHandler修飾的方法

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
        for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
            for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                addExceptionMapping(exceptionType, method);
            }
        }
    }

尋找@ExceptionHandler的function

public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
            AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);

advice排序原理

ControllerAdviceBean會實現Ordered接口,ControllerAdviceBean中的order字段會取被@RestControllerAdvice修飾的對象中@Order註解中的value

默認都是最低優先級

public interface Ordered {
/**
 * Useful constant for the highest precedence value.
 * @see java.lang.Integer#MIN_VALUE
 */
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/**
 * 低優先級
 * Useful constant for the lowest precedence value.
 * @see java.lang.Integer#MAX_VALUE
 */
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

/**
 * Get the order value of this object.
 * <p>Higher values are interpreted as lower priority. As a consequence,
 * the object with the lowest value has the highest priority (somewhat
 * analogous to Servlet {@code load-on-startup} values).
 * <p>Same order values will result in arbitrary sort positions for the
 * affected objects.
 * @return the order value
 * @see #HIGHEST_PRECEDENCE
 * @see #LOWEST_PRECEDENCE
 */
int getOrder();


/**
 * Useful constant for the highest precedence value.
 * @see java.lang.Integer#MIN_VALUE
 */
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/**
 * 低優先級
 * Useful constant for the lowest precedence value.
 * @see java.lang.Integer#MAX_VALUE
 */
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

/**
 * Get the order value of this object.
 * <p>Higher values are interpreted as lower priority. As a consequence,
 * the object with the lowest value has the highest priority (somewhat
 * analogous to Servlet {@code load-on-startup} values).
 * <p>Same order values will result in arbitrary sort positions for the
 * affected objects.
 * @return the order value
 * @see #HIGHEST_PRECEDENCE
 * @see #LOWEST_PRECEDENCE
 */
int getOrder();

}

小結

使用@Order註解設置bean的優先級是有用的。

⚠️:value越小優先級越高

選擇advice的邏輯

方法:ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()

只要advice.isApplicableToBeanType(handlerType)是true即可

for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
   ControllerAdviceBean advice = entry.getKey();
   if (advice.isApplicableToBeanType(handlerType)) {
      ExceptionHandlerMethodResolver resolver = entry.getValue();
     //匹配異常處理方法
      Method method = resolver.resolveMethod(exception);
      if (method != null) {
         return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
      }
   }
}

根據異常匹配處理方法

private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
   List<Class<? extends Throwable>> matches = new ArrayList<>();
   for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
      if (mappedException.isAssignableFrom(exceptionType)) {
         matches.add(mappedException);
      }
   }
   if (!matches.isEmpty()) {
      matches.sort(new ExceptionDepthComparator(exceptionType));
     //取排序後的第一個method
      return this.mappedMethods.get(matches.get(0));
   }
   else {
      return null;
   }
}

@Exceptionhandle修飾的方法排序邏輯

遞歸查詢exceptionhandle中的Exception類型與當前異常類型查的層級數。

總的來説就是離當前異常越近,優先級越高。

comparetor:ExceptionDepthComparator

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);

}

判斷handlerType(controller)是否應該由當前ControllerAdvice來處理

判斷ControllerAdvice的生效範圍是否包含報錯的controller

/**
 * Check whether the given bean type should be assisted by this
 * {@code @ControllerAdvice} instance.
 * @param beanType the type of the bean to check
 * @since 4.0
 * @see org.springframework.web.bind.annotation.ControllerAdvice
 */
public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
   return this.beanTypePredicate.test(beanType);
}
判斷邏輯
private boolean hasSelectors() {
   return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}
這幾個參數的來源

在ControllerAdviceBean的構造函數中初始化

ControllerAdvice annotation = (beanType != null ?
      AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class) : null);

if (annotation != null) {

this.beanTypePredicate = HandlerTypePredicate.builder()

.basePackage(annotation.basePackages())

.basePackageClass(annotation.basePackageClasses())

.assignableType(annotation.assignableTypes())

.annotation(annotation.annotations())

.build();
原因

沒有指定basePackages那麼這個advice對所有類都生效。指定了要判斷當前報異常的類是否在basePackages中。

總結

  1. 當classpath中有多個@RestControllerAdvice是,可以使用@Order指定順序
  2. ExceptionHandlerExceptionResolver只會選擇第一個匹配的advice執行,多餘的不會執行
  3. 使用@ExceptionHandle精確指定Exception是有用的。advice會精準執行該方法