题记

本文由以下三部分组成:
第一部分,介绍SpringMVC中异常处理方式有哪些并贴出相应的使用方式;
第二部分,常见问题分析;
第三部分,底层实现原理分析。

SpringMVC异常处理机制种类

SpringMVC中的异常处理机制大致分为两类:一类是Controller级别的异常处理;另一类是全局级别的异常处理。

Controller级别的异常处理

Controller级别的异常处理很简单,只需要在XxxController中定义一个异常处理方法,然后添加上@ExceptionHandler注解即可。

@RestController
public class HelloController {@GetMapping("/hello")public String hello(){System.out.println(1 / 0);return "hello!";}@ExceptionHandlerpublic String handlerException(Exception e){e.printStackTrace();return this.getClass().getName() + " 发生了异常";}}

这种方式的异常处理只能作用于当前Controller中方法,并且异常类型也是可以处理的,如果当前Controller中方法抛出的异常的不能处理,那么依然会去执行全局异常处理器中相应的异常处理方法。

全局异常处理机制

这种全局异常处理机制使用方式也很简单,只需要在类上添加@RestControllerAdvice或者@tControllerAdvice注解即可,并通过其basePackages属性来指定要处理的包(不指定也可以,但可能会出现问题,在源码分析阶段会解释)。

@RestControllerAdvice(basePackages = "com.springboot.mockito.demo")
public class GlobalExceptionAdvice {@ExceptionHandlerpublic 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 {@ExceptionHandlerpublic 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 {@ExceptionHandlerpublic 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.classClass<?> 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或者HeadString 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 forwardreturn;}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的ClasshandlerType = 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的反射机制来执行异常处理方法。

SpringMVC全局异常处理机制常见问题及底层实现分析相关推荐

  1. bean加载时调用@value时会出现空指针异常_SpringMVC全局异常处理机制

    SpringMVC全局异常处理 SpringMVC除了可以做URL映射和请求拦截外,还可以做全局异常的处理.全局异常处理可能我们平时比较少机会接触,但是每个项目都肯定会做这个处理.比如在上一间公司,是 ...

  2. 服务端增加WCF服务全局异常处理机制

    转自:http://www.csframework.com/archive/1/arc-1-20150109-2193.htm 服务端增加WCF服务全局异常处理机制,任一WCF服务或接口方式出现异常, ...

  3. springmvc全局异常处理ControllerAdvice区分返回响应类型是页面还是JSON

    springmvc全局异常处理ControllerAdvice区分返回响应类型是页面还是JSON 参考文章: (1)springmvc全局异常处理ControllerAdvice区分返回响应类型是页面 ...

  4. SpringMVC 全局异常处理的简单应用

    2019独角兽企业重金招聘Python工程师标准>>> 在SpringMVC框架的项目开发过程中,你还在使用 try{} catch(){} 输出异常吗?,那样你就真的OUT了,Sp ...

  5. SpringMVC 全局异常处理,返回json

    2019独角兽企业重金招聘Python工程师标准>>> 1.在spring-mvc.xml中增加配置: 比如我的freemarker视图定义的是:/WEB-INF/template ...

  6. springMVC 全局异常处理

    spring3.0注解很方便强大,所以更多的开发者都倾向于用注解来代替原来繁琐的配置,而对于异常也有相应的注解,我个人并不觉得在配置文件中配置全局异常很麻烦,如果整个项目都用了注解,而你再用配置就显得 ...

  7. springmvc全局异常处理

    1.自定义异常类与自定义异常处理器 1).自定义异常类 /** *自定义异常类继承Exception */ public class SysException extends Exception {p ...

  8. springmvc高级(拦截器,全局异常处理,文件上传)

    SpringMVC 1.文件上传 文件上传: 指的就是将用户本地计算机中文件上传到服务器上的过程称之为文件上传 1).文件上传编程步骤 # 1.项目中引入相关依赖 <dependency> ...

  9. Struts2拦截器实现异常处理机制

    http://bbs.itcast.cn/thread-10364-1-1.html Struts2拦截器实现异常处理机制   在j2ee项目中,系统内部难免会出现一些异常,如果把异常放任不管直接打印 ...

  10. java提供两种处理异常的机制_浅析Java异常处理机制

    关于异常处理的文章已有相当的篇幅,本文简单总结了Java的异常处理机制,并结合代码分析了一些异常处理的最佳实践,对异常的性能开销进行了简单分析. 博客另一篇文章<[译]Java异常处理的最佳实践 ...

最新文章

  1. phpadmin试用
  2. 再来 10 个新鲜的 HTML5 教程
  3. 腾讯数字生态大会倒计时4天:请收下这份最全的TEG参会攻略~
  4. UI5 repository mapping and Component-preload.js UI5RepositoryPathMapping.xml
  5. linux文件的时间格式
  6. JSP实例-彩色验证码
  7. android UI设计属性中英对照表(未修订)
  8. 开源的无客户端桌面远程网关 Apache Guacamole 被曝多个严重漏洞,可导致 RCE
  9. web.xml的简单解释以及Hello1中web.xml的简单分析
  10. jQuery UI DatepickerDatetimepicker添加 时-分-秒 并且,判断
  11. weiapi2.2 HelpPage自动生成接口说明文档和接口测试功能
  12. paip..net VS2010提示当前上下文中不存在名称的解决
  13. 电脑显示已连接网络但是无internet访问
  14. 模COMSOL Multiphysics v5.3 Win64 Linux64 MacOSX64 1DVD
  15. 贝叶斯网络结构学习方法简介
  16. React实现简单图片放大缩小旋转还原模块
  17. Ubuntu VirtualBox虚拟机安装win7 win10全过程
  18. JavaWeb(JSP中的JSTL核心标签学习) c:foreach报错500? 错误原因:ArrayList<String> people = new ArrayList<>()
  19. Docker 登录官方仓库
  20. HDOJ中的a+b问题汇总

热门文章

  1. C/C++[codeup 1397,2020]查找
  2. 阿里云云计算:4 阿里云产品架构
  3. 易筋SpringBoot 2.1 | 第廿八篇:SpringBoot之循环引用Circular Dependency
  4. matlab 风机 功率曲线,风力发电机功率曲线统计MATLAB代码实现.docx
  5. java 简易扫雷_JAVA基础课程设计 简易扫雷
  6. 可访问性之于类和对象
  7. 一元线性回归原理及python简单实现
  8. mysql导入.sql文件
  9. 在队列同步器中,同步队列为什么是双向链表,而等待队列是单链表?
  10. 机器学习之-BoostedTree