源码分析
一、构造函数
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;
}
}