@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();
}
小结
使用@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中。
总结
- 当classpath中有多个@RestControllerAdvice是,可以使用@Order指定顺序
- ExceptionHandlerExceptionResolver只会选择第一个匹配的advice执行,多余的不会执行
- 使用@ExceptionHandle精确指定Exception是有用的。advice会精准执行该方法