目录
2.ExceptionHandlerExceptionResolver异常解析类
3.ExceptionHandlerMethodResolver解析@ExceptionHandler注解
4.2 HandlerExceptionResolver异常解析类
4.3 ExceptionHandlerExceptionResolver获取调用异常处理方法
一、简介
@ControllerAdvice注解从名字上就可以看出来这个是针对Controller的切面增强处理注解,类似与@Controller和@RestController一样,@ControllerAdvice也有对应的@RestControllerAdvice注解用来返回序列化之后的对象。
@ControllerAdvice实际上也是一个Component,因为该注解同时也被@Component注解,搭配这个注解通常有一个注解和两个接口:
- @ExceptionHandler注解:统一处理从Controller抛出的异常,在@ControllerAdvice中可以拥有多个@ExceptionHandler和实现方法,SpringMVC将会自动判断哪个异常优先处理;
- RequestBodyAdvice接口:用来对Request请求方法体和对应方法参数进行处理,如果方法参数有多个,将会顺序调用到接口方法中来;
- ResponseBodyAdvice接口:类似RequestBodyAdvice,只是处理的对象变成了Response响应,处理的对象包括请求体和返回类型。
对于@ExceptionHandler的使用,可以参照(一)Java使用奇技淫巧之枚举+自定义异常+断言减少if-throw异常抛出文章,而其它两个接口则可以对请求体和返回体进行统一的处理,如整个项目需要对请求和返回进行加解密,则可以使用这两个接口对请求体和响应进行切面处理。
另外需要注意的是,@ControllerAdvice注解是一定需要搭配其它的注解或者接口使用的,否则就算这个注解类被识别到,没有具体的处理方法,SpringMVC是不会进行任何操作的。
二、@ControllerAdvice注解的处理获取
1.@ControllerAdvice注解
在分析具体实现原理前先看到其源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
// 和下面的basePackages代表的是同一个意思,只针对某些包下的类
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
// 只处理某些类所在包路径
Class<?>[] basePackageClasses() default {};
// 处理某些类和这些类的子类
Class<?>[] assignableTypes() default {};
// 处理被某些注解过的类
Class<? extends Annotation>[] annotations() default {};
}
2.ControllerAdviceBean类
这个类便是@ControllerAdvice注解对应的实体类,在这个类里面将会完成从bean工厂中获取所有被@ControllerAdvice注解过的bean,并读取注解属性将其全部返回的操作。部分关键源码如下:
public class ControllerAdviceBean implements Ordered {
// 这个bean指的就是被@ControllerAdvice注解的类对象
private final Object bean;
// bean工厂,要从bean工厂中获取bean和判断bean的类型
@Nullable
private final BeanFactory beanFactory;
// 这个类有兴趣的可以去看下,代码不多,大致作用便是存放并且判断注解的属性
private final HandlerTypePredicate beanTypePredicate;
private ControllerAdviceBean(Object bean,
@Nullable BeanFactory beanFactory) {
this.bean = bean;
this.beanFactory = beanFactory;
Class<?> beanType;
// 设置bean和beanType等属性
if (bean instanceof String) {
String beanName = (String) bean;
beanType = this.beanFactory.getType(beanName);
this.order = initOrderFromBeanType(beanType);
} else {
beanType = bean.getClass();
this.order = initOrderFromBean(bean);
}
// 从bean类型获取ControllerAdvice注解
ControllerAdvice annotation = (beanType != null ?
AnnotatedElementUtils.findMergedAnnotation(beanType,
ControllerAdvice.class) : null);
// 如果注解不为空则将注解的属性通过HandlerTypePredicate的构造器放入到
// HandlerTypePredicate对象中,以便后续判断注解属性
if (annotation != null) {
this.beanTypePredicate = HandlerTypePredicate.builder()
.basePackage(annotation.basePackages())
.basePackageClass(annotation.basePackageClasses())
.assignableType(annotation.assignableTypes())
.annotation(annotation.annotations())
.build();
} else {
// 创建一个默认的HandlerTypePredicate对象
this.beanTypePredicate = HandlerTypePredicate.forAnyHandlerType();
}
}
public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
// 这个便是外部调用判断注解属性是否满足要求的方法
return this.beanTypePredicate.test(beanType);
}
public static List<ControllerAdviceBean> findAnnotatedBeans(
ApplicationContext context) {
// 这个方法大致作用便是从bean工厂中获取所有被@ControllerAdvice注解过的
// bean对象,因为ControllerAdvice中也有@Component注解,当获得bean之后
// 再把这个bean使用ControllerAdviceBean的构造函数实例化
return Arrays.stream(BeanFactoryUtils
.beanNamesForTypeIncludingAncestors(context, Object.class))
.filter(name -> context.findAnnotationOnBean(name,
ControllerAdvice.class) != null)
.map(name -> new ControllerAdviceBean(name, context))
.collect(Collectors.toList());
}
}
三、搭配@ExceptionHandler注解统一处理异常
其大致流程图为:
1.@ExceptionHandler注解
先看下其注解源码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
}
可以看到这里面只有一个value属性,可以支持同一个注解处理多个异常类。
2.ExceptionHandlerExceptionResolver异常解析类
在DispatcherServlet中,当调用Controller中的方法抛出异常时,则会调用这个类来判断异常的类型,进而选择@ExceptionHandler注解中对应异常的处理方法。其部分关键源码如下:
public class ExceptionHandlerExceptionResolver
extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
@Override
public void afterPropertiesSet() {
// 这个方法便是调用ControllerAdviceBean类的findAnnotatedBeans方法
// 来获取bean工厂中所有被@ControllerAdvice注解过的bean对象
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
// 如果参数解析器为空则获取默认的
List<HandlerMethodArgumentResolver> resolvers =
getDefaultArgumentResolvers();
this.argumentResolvers =
new HandlerMethodArgumentResolverComposite()
.addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
// 如果处理返回值的处理器为空则获取默认的,经常使用的
// RequestResponseBodyMethodProcessor便是在这里面添加的
List<HandlerMethodReturnValueHandler> handlers =
getDefaultReturnValueHandlers();
this.returnValueHandlers =
new HandlerMethodReturnValueHandlerComposite()
.addHandlers(handlers);
}
}
private void initExceptionHandlerAdviceCache() {
// 如果spring上下文为空则无需进行下面的操作,因为都是基于bean工厂操作的
if (getApplicationContext() == null) {
return;
}
// 调用ControllerAdviceBean的方法,获取bean工厂中所有@ControllerAdvice
// 注解bean对象
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean
.findAnnotatedBeans(getApplicationContext());
// 进行排序,排序的依据便是根据@Order等排序类来完成
AnnotationAwareOrderComparator.sort(adviceBeans);
// 对获取到的切面对象进行遍历
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException();
}
// ExceptionHandlerMethodResolver 类的构造函数将会判断这个bean中的
// 方法是否含有@ExceptionHandler注解,如果有则添加到mappedMethods
// 集合中
ExceptionHandlerMethodResolver resolver =
new ExceptionHandlerMethodResolver(beanType);
// 判断前面解析后得到的mappedMethods集合是否为空
if (resolver.hasExceptionMappings()) {
// 如果不为空则将异常处理方法添加到exceptionHandlerAdviceCache
// 缓存中
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
// 如果beanType实现了ResponseBodyAdvice接口则把切面对象添加到
// responseBodyAdvice集合中,这里判断ResponseBodyAdvice接口是因为
// 我们可以把ExceptionHandler看成一个特殊的只处理异常的控制器,所有
// 抛出的异常都会在这里捕捉,并且处理然后像普通的控制器方法一样返回
// 因此这里可以使用ResponseBodyAdvice切面来处理返回数据体,但是
// 对这个这个接口实现本篇暂不介绍,下篇再分析
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
}
}
3.ExceptionHandlerMethodResolver解析@ExceptionHandler注解
前面我们说了ExceptionHandlerMethodResolver将会解析被@ExceptionHandler注解的方法,因此这里稍微分析一下其组成和解析方式,部分关键源码如下:
public class ExceptionHandlerMethodResolver {
// 用来解析方法注解@ExceptionHandler使用lambda实现的的MethodFilter对象
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
// 从传入的方法或者类解析获取到的被@ExceptionHandler注解的方法
private final Map<Class<? extends Throwable>, Method> mappedMethods =
new HashMap<>(16);
// 这个方法将会缓存具体的异常和对应的方法处理对象,以防止每次都要判断
private final Map<Class<? extends Throwable>, Method>
exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 在ExceptionHandlerExceptionResolver类中解析bean类型时调用的方法
// 对传入的class对象方法使用MethodFilter进行遍历获取注解方法
for (Method method :
MethodIntrospector.selectMethods(handlerType,
EXCEPTION_HANDLER_METHODS)) {
// 遍历判断处理的异常是否在@ExceptionHandler注解中
for (Class<? extends Throwable> exceptionType :
detectExceptionMappings(method)) {
// 将获取到的处理异常方法添加到mappedMethods集合中
addExceptionMapping(exceptionType, method);
}
}
}
@Nullable
public Method resolveMethod(Exception exception) {
// 这个方法将在调用控制器方法抛出异常时使用系统抛出的异常类型
// 获取处理这种异常的方法
return resolveMethodByThrowable(exception);
}
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
// 先调用一次方法来使用exception异常对象获取一次
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
// 如果使用传入进来的异常对象exception获取的方法为空,则使用cause
// 对象再判断一次
Throwable cause = exception.getCause();
if (cause != null) {
// 再调用判断一次,如果为空则说明这种异常不需要处理
method = resolveMethodByExceptionType(cause.getClass());
}
}
return method;
}
@Nullable
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;
}
@Nullable
private Method getMappedMethod(
Class<? extends Throwable> exceptionType) {
// 等下用来存储已经匹配到的异常类型
List<Class<? extends Throwable>> matches = new ArrayList<>();
// 从mappedMethods集合中获取从@ExceptionHandler注解解析出来的异常类型
for (Class<? extends Throwable> mappedException :
this.mappedMethods.keySet()) {
// 如果@ExceptionHandler注解的异常类型是系统抛出的异常类型是父类接口
// 则将@ExceptionHandler注解的异常类型添加到matches集合中
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 如果不为空
if (!matches.isEmpty()) {
// ExceptionDepthComparator实际上是根据异常的深度来排序的,如果
// 匹配到的异常深度越高则排名越靠前,排序后的第一位则一定是离系统
// 抛出异常最近的异常类型,直接取第一位即可
matches.sort(new ExceptionDepthComparator(exceptionType));
return this.mappedMethods.get(matches.get(0));
} else {
return null;
}
}
}
4.系统抛出异常调用链
在我们使用Controller时,系统抛出异常是再常见不过的事情了,而SpringMVC如何捕获到系统的异常再使用@ControllerAdvice注解的异常切面增强器来对异常进行处理的呢?在了解SpringMVC调用链时需要有DispatcherServlet调用链的基础,可以跳转到(四)SpringMVC原理解析之运行流程源码分析文章了解。接下来看下其部分关键源码。
4.1 DispatcherServlet入口
部分源码如下:
public class DispatcherServlet extends FrameworkServlet {
private void processDispatchResult(HttpServletRequest request,
HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler,
@Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 由于在关于SpringMVC源码分析已经大致分析过处理异常的地方
// 因此这里直接从开始解析异常处理的地方分析
boolean errorView = false;
// 如果异常不为空,则说明在Controller或者其它控制器中抛出了异常
if (exception != null) {
// 如果异常类型是ModelAndView异常,则直接获取ModelAndView对象即可
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception)
.getModelAndView();
} else {
// 其它类型的异常则需要获取handler处理器进行相应的处理
Object handler = (mappedHandler != null ?
mappedHandler.getHandler() : null);
// 处理异常
mv = processHandlerException(request, response, handler,
exception);
errorView = (mv != null);
}
}
// 对获取到的mv判断是否需要渲染视图,略过
...
}
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest
request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// 成功和错误响应可能使用不同的内容类型
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
ModelAndView exMv = null;
// 如果处理异常的集合对象不为空
if (this.handlerExceptionResolvers != null) {
// 遍历handlerExceptionResolvers异常处理集合
for (HandlerExceptionResolver resolver :
this.handlerExceptionResolvers) {
// 调用解析异常方法,如果返回不为空则退出,resolveException将是
// 具体解析异常的地方
exMv = resolver.resolveException(request, response,
handler, ex);
if (exMv != null) {
break;
}
}
}
// 对获取到的异常ModelAndView进行判断处理,略过
...
}
}
4.2 HandlerExceptionResolver异常解析类
部分源码如下:
public abstract class AbstractHandlerExceptionResolver
implements HandlerExceptionResolver, Ordered {
@Override
@Nullable
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
// 判断这个类型的handler处理器是否支持
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
// 调用解析异常方法
ModelAndView result = doResolveException(request, response,
handler, ex);
return result;
}
else {
return null;
}
}
}
public abstract class AbstractHandlerMethodExceptionResolver
extends AbstractHandlerExceptionResolver {
@Override
@Nullable
protected final ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) {
return doResolveHandlerMethodException(request, response,
(HandlerMethod) handler, ex);
}
}
4.3 ExceptionHandlerExceptionResolver获取调用异常处理方法
这个类继承了AbstractHandlerMethodExceptionResolver类,部分源码如下:
public class ExceptionHandlerExceptionResolver
extends AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(
HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerMethod handlerMethod, Exception exception) {
// 调用getExceptionHandlerMethod方法获取具体处理方法对象,这里是关键
ServletInvocableHandlerMethod exceptionHandlerMethod =
getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
// 中间可略过,无非是设置argumentResolvers和returnValueHandlers对象
try {
Throwable cause = exception.getCause();
// 根据cause异常是否为空来判断具体调用哪个方法,调用进
// ServletInvocableHandlerMethod对象的invokeAndHandle方法就和
// 正常调用controller方法一样了
if (cause != null) {
exceptionHandlerMethod.invokeAndHandle(webRequest,
mavContainer, exception, cause, handlerMethod);
}
else {
exceptionHandlerMethod.invokeAndHandle(webRequest,
mavContainer, exception, handlerMethod);
}
}
// 后面可略过
...
}
@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
// 如果handlerMethod不为空则先根据handlerMethod对象来获取
if (handlerMethod != null) {
handlerType = handlerMethod.getBeanType();
// 从缓存中先获取
ExceptionHandlerMethodResolver resolver =
this.exceptionHandlerCache.get(handlerType);
// 如果为空在实例化一个ExceptionHandlerMethodResolver对象
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
// 调用前面说过的resolveMethod方法来获取具体解析异常的方法
Method method = resolver.resolveMethod(exception);
if (method != null) {
// 不为空则返回封装了方法的ServletInvocableHandlerMethod对象
return new ServletInvocableHandlerMethod(
handlerMethod.getBean(), method);
}
// 如果这里判断失败了则会使用后面的Advice来判断,因此需要先使其支持
// advice切面代理
if (Proxy.isProxyClass(handlerType)) {
handlerType=AopUtils.getTargetClass(handlerMethod.getBean());
}
}
// 循环遍历获取到的exceptionHandlerAdviceCache异常解析切面对象
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver>
entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
// 调用前面说过的isApplicableToBeanType方法判断是否满足要求
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
// 使用resolveMethod方法解析出具体处理异常的方法对象
Method method = resolver.resolveMethod(exception);
if (method != null) {
// 封装成ServletInvocableHandlerMethod对象返回
return new ServletInvocableHandlerMethod(
advice.resolveBean(), method);
}
}
}
return null;
}
}
当在ExceptionHandlerExceptionResolver类中获取了具体的HandlerMethod并且调用了ServletInvocableHandlerMethod类的invokeAndHandle方法执行完后就标志着@ExceptionHandler注解的方法已经被执行过,系统的异常处理已被捕获,随后将会把方法返回的数据返回给前端。