题记
本文由以下三部分组成:
第一部分,介绍SpringMVC中异常处理方式有哪些并贴出相应的使用方式;
第二部分,常见问题分析;
第三部分,底层实现原理分析。
SpringMVC异常处理机制种类
SpringMVC中的异常处理机制大致分为两类:一类是Controller级别的异常处理;另一类是全局级别的异常处理。
Controller级别的异常处理
Controller级别的异常处理很简单,只需要在XxxController中定义一个异常处理方法,然后添加上@ExceptionHandler注解即可。
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
System.out.println(1 / 0);
return "hello!";
}
@ExceptionHandler
public String handlerException(Exception e){
e.printStackTrace();
return this.getClass().getName() + " 发生了异常";
}
}
这种方式的异常处理只能作用于当前Controller中方法,并且异常类型也是可以处理的,如果当前Controller中方法抛出的异常的不能处理,那么依然会去执行全局异常处理器中相应的异常处理方法。
全局异常处理机制
这种全局异常处理机制使用方式也很简单,只需要在类上添加@RestControllerAdvice或者@tControllerAdvice注解即可,并通过其basePackages属性来指定要处理的包(不指定也可以,但可能会出现问题,在源码分析阶段会解释)。
@RestControllerAdvice(basePackages = "com.springboot.mockito.demo")
public class GlobalExceptionAdvice {
@ExceptionHandler
public String resolveException(Exception e){
return "GlobalExceptionAdvice.resolveException";
}
}
如果既在Controller中指定了异常处理方法又在全局异常处理器中指定了异常处理方法,那么当Controller中抛出一个两者都可以处理的异常时,谁生效呢?
答案是,Controller中指定的异常处理方法生效,这也是就近原则的体现。
可以测试一把,前面我们再hello方法中执行除零逻辑,因此会抛出by zero异常。我们看下返回的是那个方法的异常提示即可。这里使用了curl方法来访问接口,可以看到返回的是在HelloController中定义的异常处理方法返回的异常提示。
curl http://localhost:8081/hello
om.springboot.mockito.demo.controller.HelloController occur exception
常见问题分析
为什么在子全局异常处理中指定的处理方法不执行?
假设存在两个全局异常处理器,分别为:GlobalExceptionAdvice、SubExceptionAdvice,其中SubExceptionAdvice 继承自GlobalExceptionAdvice。
GlobalExceptionAdvice代码:
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
public String resolveException(Exception e) {
return "GlobalExceptionAdvice.resolveException";
}
}
SubExceptionAdvice代码 :
@RestControllerAdvice
public class SubExceptionAdvice extends GlobalExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
public String exceptionHandler(ArithmeticException e){
return "invoke SubExceptionAdvice.exceptionHandler";
}
}
我们先把HelloController中的异常处理方法去掉,再去访问hello方法。期望的是SubExceptionAdvice的exceptionHandler方法执行,因为在该方法上通过@ExceptionHandle已经声明了具体异常处理类型,而除零所抛出的异常正是该类型(ArithmeticException)。
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
System.out.println(1 / 0);
return "hello!";
}
}
但是可以发现生效的依然是父类的resolveException方法。
curl http://localhost:8081/hello
GlobalExceptionAdvice.resolveException
如何处理呢?
解决办法有两种,一种是通过设置GlobalExceptionAdvice以及SubExceptionAdvice中的@RestControllerAdvice注解的basePackages属性设值,来划分要处理的不同包。
@RestControllerAdvice("com.springboot.xxx.service")
public class GlobalExceptionAdvice {
@ExceptionHandler
public String resolveException(Exception e) {
return "GlobalExceptionAdvice.resolveException";
}
}
@RestControllerAdvice("com.springboot.xxx.controller")
public class SubExceptionAdvice extends GlobalExceptionAdvice {
@ExceptionHandler(ArithmeticException.class)
public String exceptionHandler(ArithmeticExceptione){
return "invoke SubExceptionAdvice.exceptionHandler";
}
}
需注意的是,如果使用这种方式,两个类的@RestControllerAdvice注解的basePackages属性都必须设置,否则可能无效。
如果不想设置basePackages属性怎么办?答案就是让某一个类实现PriorityOrdered接口,并重写其getOrder方法,该方法返回值越小,该类中的异常处理方法就越先被执行。
@RestControllerAdvice
public class SubExceptionAdvice extends GlobalExceptionAdvice implements PriorityOrdered{
@ExceptionHandler(ArithmeticException.class)
public String exceptionHandler(ArithmeticExceptione){
return "invoke SubExceptionAdvice.exceptionHandler";
}
public int getOrder() {
return 0;
}
}
测试一波,可以看到是SubExceptionAdvice的exceptionHandler方法执行。
curl http://localhost:8081/hello
invoke SubExceptionAdvice.exceptionHandler
SpringMVC异常处理机制原理分析
异常处理属于SpringMVC的核心功能之一,并且有专门的异常处理器接口-HandlerExceptionResolver,通常使用的都是ExceptionHandlerExceptionResolver这个实现类。
先看下ExceptionHandlerExceptionResolver的类关系图:
通过类关系图可以看出该类除了实现HandlerExceptionResolver接口之外,还实现了InitializingBean、ApplicationContext接口,这意味着该类必然要实现afterPropertiesSet以及setApplicationContext方法。
首先看下其实现的setApplicationContext方法,比较简单,就是将传入的ApplicationContext实现类实例赋值给实例变量applicationContext 。
public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
重点是其实现的afterPropertiesSet方法,因为在该方法中完成了当前工程中所有添加了@RestControllerAdvice或者@tControllerAdvice注解的类的解析。
本次分析的重点是在该方法中调用的initExceptionHandlerAdviceCache方法,剩余的逻辑都是在设置方法参数解析器或者方法返回值解析器,不属于本次分析范围之内。
// ExceptionHandlerExceptionResolver#afterPropertiesSet
public void afterPropertiesSet() {
// 重点!!!初始化全局异常处理器
initExceptionHandlerAdviceCache();
if (this.argumentResolvers == null) {
// 设置方法参数解析,非重点
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
// 设置方法返回值解析,非重点
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
由以上代码可以得知,afterPropertiesSet方法分析的重点就是其调用的initExceptionHandlerAdviceCache方法。
// ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
private void initExceptionHandlerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
// 通过ControllerAdviceBean的findAnnotatedBeans方法来找到当前上下文中添加了@ControllerAdvice注解的Bean。
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
for (ControllerAdviceBean adviceBean : adviceBeans) {
// 获取全局异常处理器的Class,例如SubExceptionAdvice.class
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
// 根据全局异常处理器的类型来创建ExceptionHandlerMethodResolver
ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
// 如果全局异常处理器中存放异常处理方法->@ExceptionHandler注解标注的方法
if (resolver.hasExceptionMappings()) {
// 保存到exceptionHandlerAdviceCache,
// key -> ControllerAdviceBean value -> ExceptionHandlerMethodResolver
this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
}
if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
this.responseBodyAdvice.add(adviceBean);
}
}
if (logger.isDebugEnabled()) {
int handlerSize = this.exceptionHandlerAdviceCache.size();
int adviceSize = this.responseBodyAdvice.size();
if (handlerSize == 0 && adviceSize == 0) {
logger.debug("ControllerAdvice beans: none");
}else {
logger.debug("ControllerAdvice beans: " +
handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
}
}
}
那么ControllerAdviceBean的findAnnotatedBeans是如何查找的呢?
// ControllerAdviceBean#findAnnotatedBeans
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
// 通过调用BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法来进行层次性(父子容器)查找,
// 由于我们通常使用父子容器,即Controller在一个应用上下文中,Service在另一个上下文中。
for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
// 这里涉及到@Scope注解的proxyMode属性,有兴趣的同学可以查看我的另一篇文章->
// 《@Scope注解的proxyMode的作用以及如何影响IoC容器的依赖查找》
if (!ScopedProxyUtils.isScopedTarget(name)) {
// 通过调用应用上下文的findAnnotationOnBean方法来判断每一个遍历到的beanName所对应的Bean是否添加了@ControllerAdvice注解
ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
if (controllerAdvice != null) {
// 如果添加了@ControllerAdvice注解,将其保存进adviceBeans集合中
adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
}
}
}
// 之前我们让SubExceptionAdvice实现PriorityOrdered接口,其执行优先级就高于GlobalExceptionAdvice的原因就在这里。因为在这里进行了排序。
OrderComparator.sort(adviceBeans);
return adviceBeans;
}
OK,我们已经知道ControllerAdviceBean是如何查找添加了@ControllerAdvice注解的Bean后,接下来我们就要分析下如何解析全局异常处理器中的异常处理方法的,这部分由ExceptionHandlerMethodResolver的构造函数来完成(不知道何时创建该对象的同学可以去看看前面贴出的initExceptionHandlerAdviceCache方法)。
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
// ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
// 首先通过MethodIntrospector的selectMethods以及EXCEPTION_HANDLER_METHODS 来找出当前类中所有添加了@ExceptionHandler注解的方法
for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
// 这里为什么是一个循环?这是因为@ExceptionHandler的value属性是一个数组,即可以指定多个异常类型
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
// 通过addExceptionMapping方法来保存探测到的可处理的异常类型以及异常处理方法
addExceptionMapping(exceptionType, method);
}
}
}
如何在全局异常处理器类中查找添加了@ExceptionHandler注解的方法的过程这里就不展开分析了,重点是如何解析添加了@ExceptionHandler注解的方法-detectExceptionMappings。
// ExceptionHandlerMethodResolver#detectExceptionMappings
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
List<Class<? extends Throwable>> result = new ArrayList<>();
// 首先通过detectAnnotationExceptionMappings方法来获取@ExceptionHandle注解中指定的异常类型
detectAnnotationExceptionMappings(method, result);
// 如果未在@ExceptionHandle注解中指定要处理的异常类型
if (result.isEmpty()) {
// 获取异常处理方法的参数。这也是为什么我们可以不在@ExceptionHandle注解中指定要处理的异常类型原因。
for (Class<?> paramType : method.getParameterTypes()) {
// 如果方法的参数类型是Throwable及其子类型,保存到result集合中
if (Throwable.class.isAssignableFrom(paramType)) {
result.add((Class<? extends Throwable>) paramType);
}
}
}
// 如果未在@ExceptionHandle注解中指定要处理的异常类型也未在方法参数中指定,抛出异常。
if (result.isEmpty()) {
throw new IllegalStateException("No exception types mapped to " + method);
}
return result;
}
// ExceptionHandlerMethodResolver#detectAnnotationExceptionMappings
private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
// 获取方法上的@ExceptionHandle注解
ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
Assert.state(ann != null, "No ExceptionHandler annotation");
// 获取@ExceptionHandle的value属性值并添加到传入的result集合中。
result.addAll(Arrays.asList(ann.value()));
}
这里有必要贴出addExceptionMapping方法,因为正是该方法决定了当存在两个或多个异常处理类型相同方法时抛出异常。
// ExceptionHandlerMethodResolver#addExceptionMapping
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
Method oldMethod = this.mappedMethods.put(exceptionType, method);
// 如果旧的方法不等于null并且旧方法和新方法不相等,抛出异常
if (oldMethod != null && !oldMethod.equals(method)) {
throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
exceptionType + "]: {" + oldMethod + ", " + method + "}");
}
}
以上只是对添加了@ControllerAdvice注解的类以及其添加了@ExceptionHandler方法的解析、注册过程的分析,可以视为准备工作,接下来才是进入使用过程。
异常处理方法的调用过程分析
要搞明白异常处理方法是何时被调用的,就要从DispatcherServlet的doDispatch方法说起。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 获取处理器执行器,返回的是一个拦截器链 + 处理器执行器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取处理器适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 检查请求方法是否是Get或者Head
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行拦截器的preHandle方法(注意这是一个拦截器链),如果有任何一个拦截器的preHandle方法返回false,请求处理结束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行处理器适配器
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 处理拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 本次分析重点!!! 处理请求执行结果。全局异常处理器的异常处理方法就是在这里被调用的
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
通过分析DispatcherServlet的doDispatch方法可以得知,无论有没有发生异常,最后都会执行processDispatchResult方法,而对于全局异常处理器的异常处理方法也是在该方法中完成调用的。
// DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 通过判断异常对象是否为空来判断在处理请求过程中是否发生了异常。
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// 处理异常
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 视图渲染
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
} else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// 执行拦截器的afterCompletion方法
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
通过分析processDispatchResult方法可以得知,其对于异常处理是通过调用processHandlerException方法来完成。
// DispatcherServlet#processHandlerException
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) {
// 遍历所有已注册的异常解析器 -> HandlerExceptionResolver接口实现类
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
// 删除与本次分析无关代码.....
}
通常使用的都是AbstractHandlerExceptionResolver的resolveException方法。
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
// 检查是否可以处理给定的处理器执行器
if (shouldApplyTo(request, handler)) {
prepareResponse(ex, response);
// 重点!!! 调用doResolveException来完成异常处理
ModelAndView result = doResolveException(request, response, handler, ex);
if (result != null) {
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
}
return result;
}
else {
return null;
}
}
ExceptionHandlerExceptionResolver实现了doResolveHandlerMethodException方法。
// ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
// 通过getExceptionHandlerMethod来获取异常处理器,其实就是全局异常处理器的异常处理方法,只不过这里将其包装成ServletInvocableHandlerMethod
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
if (this.argumentResolvers != null) {
// 设置异常处理器的方法参数解析器
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
// 设置异常处理器的方法返回值解析器
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
Throwable cause = exception.getCause();
if (cause != null) {
// 执行异常处理器,其实就是用户自定义的异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
} else {
// 执行异常处理器,其实就是用户自定义的异常处理方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
} catch (Throwable invocationEx) {
if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
return null;
}
// 删除与本次分析无关代码...
}
doResolveHandlerMethodException方法的重点是如何获取异常处理器(getExceptionHandlerMethod)以及如何执行获取到的异常处理器(invokeAndHandle)。
我们首先来分析下getExceptionHandlerMethod方法。
// ExceptionHandlerExceptionResolver#getExceptionHandlerMethod
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
// 获取处理器执行器所持有的Bean类型,其实就是XxxController的Class
handlerType = handlerMethod.getBeanType();
// 重点来了,为什么我们在Controller中指定的异常处理方法优先于全局异常处理器的异常处理方法执行,原因就在这里。
// 首先根据给定的Bean类型(例如HelloController.class)去缓存中获取对应的ExceptionHandlerMethodResolver
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
// 如果获取到的ExceptionHandlerMethodResolver 等于null,则创建一个新的ExceptionHandlerMethodResolver并保存进exceptionHandlerCache缓存中
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
// 调用对应的ExceptionHandlerMethodResolver的resolveMethod方法
Method method = resolver.resolveMethod(exception);
if (method != null) {
// 如果返回的Method 对象不为空,就意味着在Controller中有对应的异常处理方法,直接结束,不会再往下执行。
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
}
//
if (Proxy.isProxyClass(handlerType)) {
handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
}
}
// 遍历所有已注册的全局异常处理器,注意这里使用的缓存exceptionHandlerAdviceCache
// 便是前面我们在分析全局异常处理器注册时是用的到的缓存
for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
ControllerAdviceBean advice = entry.getKey();
// 判断全局异常处理器是否能处理给定的Bean类型
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = entry.getValue();
// 调用对应的ExceptionHandlerMethodResolver的resolveMethod方法。
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
}
}
}
return null;
}
通过分析ExceptionHandlerExceptionResolver的getExceptionHandlerMethod方法,可以得知该方法的重点就两个:一个是ExceptionHandlerMethodResolver的resolveMethod方法(根据异常类型来获取对应的异常处理方法),另一个就是ControllerAdviceBean的isApplicableToBeanType(判断ControllerAdviceBean是否能适用于给定的Class)方法。
首先我们来分析下ExceptionHandlerMethodResolver的resolveMethod方法。
// ExceptionHandlerMethodResolver#resolveMethod
public Method resolveMethod(Exception exception) {
return resolveMethodByThrowable(exception);
}
可以发现,在resolveMethod方法中没有做任何逻辑处理,直接调用resolveMethodByThrowable方法。
// ExceptionHandlerMethodResolver#resolveMethodByThrowable
public Method resolveMethodByThrowable(Throwable exception) {
// 先根据异常类型去查找对应的异常处理方法
Method method = resolveMethodByExceptionType(exception.getClass());
if (method == null) {
// 判断是否是因为别的异常导致发生了当前异常->IllegalArgumentException exception = new IllegalArgumentException("",new NumberFormatException())
Throwable cause = exception.getCause();
if (cause != null) {
// 最后根据getCause方法所返回的异常类型再次尝试获取对应的异常处理方法
method = resolveMethodByExceptionType(cause.getClass());
}
}
return method;
}
在resolveMethodByThrowable方法也是通过调用resolveMethodByExceptionType方法来完成根据异常类型获取对应异常处理方法。
// ExceptionHandlerMethodResolver#resolveMethodByExceptionType
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
// 首先根据异常类型去exceptionLookupCache缓存中获取对应的异常处理方法
Method method = this.exceptionLookupCache.get(exceptionType);
if (method == null) {
// 如果未在缓存中找到对应的异常处理方法,再调用exceptionLookupCache方法来获取
method = getMappedMethod(exceptionType);
// 最后将getMappedMethod方法的处理结果保存到exceptionLookupCache中。
this.exceptionLookupCache.put(exceptionType, method);
}
return method;
}
resolveMethodByExceptionType方法也没有完成真正的异常方法获取,而是委派给getMappedMethod方法来完成。
// ExceptionHandlerMethodResolver#getMappedMethod
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
// mappedMethods这个Map中的数据是什么时候保存进去的?
// 忘记的同学可以去看下前面我们对于ExceptionHandlerMethodResolver构造函数的分析
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
// 判断传入的异常类型是否是已注册的可处理的异常类型或者其子类型。
// 例如发生的异常类型为->NumberFormatException,那么通过@ExceptionHandler注解声明的
// IllegalArgumentException或者RuntimeException都可以。
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 如果匹配的已注册异常类型有多个
if (!matches.isEmpty()) {
// 排序。使用的比较器为ExceptionDepthComparator。比较器实现源码这里就不展开分析了。
// 这里只大致讲解下比较器(ExceptionDepthComparator)的处理思路。
// 其实思路就是比较对于给定的异常类型到已声明的可处理异常类型之间的层级(继承层次)最短。
// 假设发生的异常类型为NumberFormatException,声明的异常处理方法有可处理IllegalArgumentException异常类型的方法以及可处理RuntimeException异常类型的方法。
// 那么只需要通过NumberFormatException的getSuperClass来获取其父类,
// 然后判断其父类是否和IllegalArgumentException或RuntimeException相等,递归查找,每查找一次,就加1。
matches.sort(new ExceptionDepthComparator(exceptionType));
// 最后使用排序好的matches中的第一个异常类型。
return this.mappedMethods.get(matches.get(0));
} else {
return null;
}
}
对于ExceptionHandlerExceptionResolver的getExceptionHandlerMethod方法就分析完毕了,接下来就分析ControllerAdviceBean的isApplicableToBeanType方法。
还记得前面我们遇到的一个问题吗?就是当存在两个全局异常处理类,并且都未指定@ControllerAdvice的basePackages属性值。这时候发生异常时,执行的异常处理方法可能并不是我们想要的。
答案就在这里揭晓。
// ControllerAdviceBean#isApplicableToBeanType
public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
return this.beanTypePredicate.test(beanType);
}
在isApplicableToBeanType方法中直接通过调用HandlerTypePredicate的test方法来完成。
public boolean test(Class<?> controllerType) {
// 重点!!! 是否可以选择,如果不可以选择直接返回true。
if (!hasSelectors()) {
return true;
} else if (controllerType != null) {
// 判断发生异常方法所属的类的全限定名是否是以@ControllerAdvice的basePackages属性值开始
for (String basePackage : this.basePackages) {
if (controllerType.getName().startsWith(basePackage)) {
return true;
}
}
// 判断发生异常方法所属的类是否是某种类型,通常不会执行到这里
for (Class<?> clazz : this.assignableTypes) {
if (ClassUtils.isAssignable(clazz, controllerType)) {
return true;
}
}
// 判断发生异常方法所属的类是否添加了某个注解,通常不会执行到这里
for (Class<? extends Annotation> annotationClass : this.annotations) {
if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
return true;
}
}
}
return false;
}
重点就是这个hasSelectors方法。
private boolean hasSelectors() {
// 如果@ControllerAdvice的basePackages属性值为空直接返回true。
// 思考一下,这会导致什么情况发生?
// 假设工程里面存在N个全局异常处理器,如果其中某一个异常处理器上的@ControllerAdvice的
// basePackages属性并未赋值,并且这个异常处理器处于exceptionHandlerAdviceCache第一个位置,
// 那么即便后面的全局异常处理器中声明的异常处理方法上的可处理异常类型更具体(而不是Exception这种兜底的异常处理方法),
// 和发生的异常类型更匹配,也不会被执行,因为一旦遇到一个未给basePackages赋值的全局异常处理器就直接返回true了
return (!this.basePackages.isEmpty() || !this.assignableTypes.isEmpty() || !this.annotations.isEmpty());
}
以上就是我们对异常处理方法获取的分析,最后再看下异常处理方法是如何被执行的,即ServletInvocableHandlerMethod的invokeAndHandle方法。
异常处理方法执行原理
在Java如何执行方法(Method)对象?
相信聪明的你一定想到了method对象的invoke方法,Bingo!SpringMVC也是这样做的。
// ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 通过invokeForRequest方法来执行目标方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 删除与本次分析无关的代码...
}
invokeAndHandle方法中最重要的就是其调用的invokeForRequest方法,剩下的都是对方法返回值处理,这里就不展开分析了。要不本篇文章篇幅就太长了,不知道能坚持看到这里的同学有几位,哈哈~
// InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 首先通过getMethodArgumentValues来对方法参数进行处理
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 通过doInvoke方法来执行目标方法
return doInvoke(args);
}
doInvoke方法很简单,就是直接调用方法对象(Method)的invoke方法,传入目标类对象以及前面处理好的方法参数。
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
} catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
throw new IllegalStateException(formatInvokeError(text, args), ex);
} catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
} else if (targetException instanceof Error) {
throw (Error) targetException;
} else if (targetException instanceof Exception) {
throw (Exception) targetException;
} else {
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
}
}
}
总结
以上就是我们对SpringMVC全局异常处理器机制的分析。指定@ControllerAdvice或@RestControllerAdvice的basePackages是一个好习惯,否则可能会发生一些预期之外的问题。但这些问题也是可以解决的,例如实现PriorityOrdered接口。
其实现是基于Spring的InitializingBean以及应用上下文来获取添加了@ControllerAdvice注解的Bean中的异常处理方法(方法上必须添加@ExceptionHandler注解)解析并保存到缓存中,通过try…catch机制,这样当XxxController的xxx方法发生异常时就可以根据异常类型找到对应的异常处理方法,最后基于Java的反射机制来执行异常处理方法。