1.开头在前
日常开发中,几乎我们的项目都会用到异常处理器,我们通常会定制属于自己的异常处理器,来处理项目中大大小小、各种各样的异常。配置异常处理器目前最常用的方式应该是使用@ControllerAdvice+@ExceptionHandler的组合来实现,当然还有其他的方式,例如实现HandlerExceptionResolver接口等等等。这次我们来看看再@ControllerAdvice和@ExceptionHandler模式下Spring是如何识别我们配置的处理器按照我们的配置进行处理,以及我们配置了两个异常处理器,而这两个异常又是父子继承关系,那么当程序中抛出子异常时,Spring又是如何选择的呢,是以子异常处理器处理还是父异常处理器处理?下面我们来跟踪源码来看看到底是怎么实现的
2.案例展示
如下代码,配置了一个全局异常处理器,针对于RuntimeException,ArithmeticException,Exception三个异常进行了特殊处理,其中ArithmeticException是RuntimeException的子类,RuntimeException是Exception的子类
@RestControllerAdvice public class YuqiExceptionHandler { @ExceptionHandler(RuntimeException.class) public String handleRuntimeException(RuntimeException e) { return "handle runtimeException"; } @ExceptionHandler(ArithmeticException.class) public String handleArithmeticException(ArithmeticException e) { return "handle ArithmeticException"; } @ExceptionHandler(Exception.class) public String handleException(Exception e) { return "handle Exception"; } }
如下代码,是我们的测试类,通过访问该请求,会触发除0的算术异常。
@RestController @RequestMapping("/test") public class TestController { @GetMapping("/exception") public void testException() { int i = 1 / 0; } }
3.源码跟踪
首先我们来看下前端控制器DispatcherServlet的执行逻辑,核心doDispatch()方法,前端请求由dispatcherServlet拦截后,经handlerMapping找到匹配的handler后转换成适合的handlerAdapter进行实际方法的调用。
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); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } //根据handler找到合适的适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 进行方法实际的调用 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { //方法调用抛出异常,将异常对象进行保存 dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. 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()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
然后我们再来看下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) {
//判断当前异常是否为ModelAndViewDefiningException的实例
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
//获取当前执行的handler(这里的时候即controller)
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
//调用processHandlerException()进行进一步的异常处理
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
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) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
接下来看processHandlerException()是如何对异常进一步进行处理的
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); // Check registered HandlerExceptionResolvers... ModelAndView exMv = null;
//如果spring容器中的handlerExceptionResolver列表不为null的话,则进行遍历,当前容器是有两个handlerExceptionResolver,如下面图所展示 if (this.handlerExceptionResolvers != null) { for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
//遍历每一个handlerExceptionResolver,并且调用其resolveException()方法尝试对当前异常进行解决 exMv = resolver.resolveException(request, response, handler, ex);
//如果返回的ModelAndView不为null,说明该handlerExceptionResolver成功解决了该异常,那么就退出循环 if (exMv != null) { break; } } }
//判断异常是否已被解决 if (exMv != null) { if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // We might still need view name translation for a plain error model... if (!exMv.hasView()) { String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { exMv.setViewName(defaultViewName); } } if (logger.isTraceEnabled()) { logger.trace("Using resolved error view: " + exMv, ex); } else if (logger.isDebugEnabled()) { logger.debug("Using resolved error view: " + exMv); } WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } throw ex; }
容器持有的handlerExceptionResolver如下,重点就是第二个,HandlerExceptionResolverComposite,我们来看看它是如何解决的
那么下一步来看HandlerExceptionResolverComposite的resolveException()方法是如何解决异常的,可以看到HandlerExceptionResolverComposite内部又包含着三个异常解析器,通过遍历该列表继续重复之前disPatcherServlet的方法中的操作,即遍历每个解析器看是否能解决这个异常。最重要的就是ExceptionHandlerExceptionResolver这个解析器,下面我们来看看它的resolveException()方法是如何处理这个异常的
ExceptionHandlerExceptionResolver本身并没有实现resolveException方法,而是直接使用它的超类AbstractHandlerExceptionResolver实现的resolveException方法,下面我们来看下内部具体逻辑
@Override @Nullable public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { //判断当前resolver是否支持对该handler的处理 if (shouldApplyTo(request, handler)) { prepareResponse(ex, response);
//进行异常处理,该方法由它的子类AbstractHandlerMethodExceptionResolver来提供 ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { // Print debug message when warn logger is not enabled. if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result)); } // Explicitly configured warn logger in logException method. logException(ex, request); } return result; } else { return null; } }
来看下AbstractHandlerMethodExceptionResolver的doResolveException()方法
protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { //将handler转换成handlerMehtod HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
//调用该方法进行对异常进行处理,这里就开始真正调用ExceptionHandlerExceptionResolver的方法了 return doResolveHandlerMethodException(request, response, handlerMethod, ex); }
看下核心解析器ExceptionHandlerExceptionResolver的doResolveHandlerMethodException()方法
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { //核心方法,获取处理该异常的HandlerMehtod,也就是前面我们配置的方法,就是说在这里真正要决定用哪个方法来处理这个异常了这里因为我们之前的@ControllerAdvice也会跟@Controller一样将类转为Handler,
里面的方法对应的也都转换为HandlerMethod 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(); ArrayList<Throwable> exceptions = new ArrayList<>(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } // Expose causes as provided arguments as well Throwable exToExpose = exception; while (exToExpose != null) { exceptions.add(exToExpose); Throwable cause = exToExpose.getCause(); exToExpose = (cause != exToExpose ? cause : null); } Object[] arguments = new Object[exceptions.size() + 1]; exceptions.toArray(arguments); // efficient arraycopy call in ArrayList arguments[arguments.length - 1] = handlerMethod; exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } catch (Throwable invocationEx) { // Any other than the original exception (or a cause) is unintended here, // probably an accident (e.g. failed assertion or the like). if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) { logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx); } // Continue with default processing of the original exception... return null; } if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } }
来看下ExceptionHandlerExceptionResolver是如何获取处理异常的指定方法的
protected ServletInvocableHandlerMethod getExceptionHandlerMethod( @Nullable HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = null; if (handlerMethod != null) { // Local exception handler methods on the controller class itself. // To be invoked through the proxy, even in case of an interface-based proxy.
//获取该HandlerMehtod实际的Bean类型,即我们Controller的类型--TestController handlerType = handlerMethod.getBeanType();
//根据该类型去excpetionHandler的缓存Map中获取解析器,此时缓存map中没有数据 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) {
//如果缓存中没有该类型的解析器就根据该类型创建一个新的解析器 resolver = new ExceptionHandlerMethodResolver(handlerType);
//放入到缓存Map中 this.exceptionHandlerCache.put(handlerType, resolver); }
//通过异常获取方法,这里获取为null Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext); } // For advice applicability check below (involving base packages, assignable types // and annotation presence), use target class instead of interface-based proxy
//判断该Bean类型是否为代理类 if (Proxy.isProxyClass(handlerType)) {
//如果是代理类我们要获取到到实际的被代理类的类型 handlerType = AopUtils.getTargetClass(handlerMethod.getBean()); } } //遍历当前容器内controllerAdvice Bean的缓存集合,缓存中当前只有一个controllerAdvice 就是我们之前配置的全局异常处理器 for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey();
//判断该controller类型是否需要被增强处理,这里主要是判断@ControllerAdvice上的三个属性basePackages即该类是否被基包路径覆盖,
assignableTypes或者被该类特殊指定,annotations以及该类上有特殊指定的注解,这三种情况有一个满足都为true,如果三个属性都为null,那么也返回true if (advice.isApplicableToBeanType(handlerType)) {
//获取该controllerAdvice对应的解析器 ExceptionHandlerMethodResolver resolver = entry.getValue();
//获取解决异常的方法 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext); } } } return null; }
ExceptionHandlerMethodResolver.resolveMethod()方法中内部嵌套调用了好几个方法,最主要的我们来看getMapperMethod
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
//遍历该解析器下对应的异常处理方法的key,我们测试类的应该是有三个方法,对应着Exception,RuntimeException,ArithmeticException三个异常 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
//判断当前遍历的异常是否等于传入的异常,或者为他的父类或超类,很显然,我们遍历的这三个都符合 if (mappedException.isAssignableFrom(exceptionType)) {
//符合就添加进列表中 matches.add(mappedException); } }
//如果列表不为空,说明有符合条件的对应方法 if (!matches.isEmpty()) {
//符合条件的方法大于1个,就对列表进行排序,这里就是确定当存在多个能够处理同一异常的方法如何选择 if (matches.size() > 1) {
//来重点看下这个自定义排序器的内部排序规则 matches.sort(new ExceptionDepthComparator(exceptionType)); }
//返回排序完列表的第一个元素,也就是深度最低的一个,跟传入异常关系最近的一个,可以理解为就近原则。 return this.mappedMethods.get(matches.get(0)); } else {
return NO_MATCHING_EXCEPTION_HANDLER_METHOD; } }
ExceptionDepthComparator的排序规则,可以看到就是计算每个异常的深度,这个深度就是和传入的异常进行比较,如何是同一个异常深度就是0,不等就深度+1然后继续用该异常的父类判断,不断递归,把列表中所有异常相对于传入异常的深度都计算完,然后升序排序。
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); }
到这里我们就看完了@ControllerAdvice+@ExceptionHandler执行流程的核心代码,完整流程的源码较多,本文没有全部展示,但是这些核心部分应该可以让我们了解SpringMVC是如何在抛出异常后,找到合适异常处理器进行处理的。
4. 拓展
在日常开发中,可能有需要到某个类的异常要这么处理,另外个类的异常要那么处理,这就相当于需要一些定制化处理,这个时候我们系统中只有一个全局异常处理器就难以满足需求,这时候我们可以定制多个全局异常处理器,在@ControllerAdvice属性上可以指定覆盖的范围,如下示例
我们在程序中定义了两个异常处理器,其中Yuqi2ExceptionHandler在@ControllerAdvice注解上的assignableTypes属性上指定了TestController,这样的话当TestController中抛出算术异常时,就会走Yuqi2ExceptionHandler的处理。这样就实现了对特殊类的定制化处理
@RestControllerAdvice public class YuqiExceptionHandler { @ExceptionHandler(ArithmeticException.class) public String handleArithmeticException(ArithmeticException e) { return "handle ArithmeticException"; } } @RestControllerAdvice(assignableTypes = {TestController.class}) public class Yuqi2ExceptionHandler { @ExceptionHandler(ArithmeticException.class) public String handleArithmeticException(ArithmeticException e) { return "handle ArithmeticException v2"; } }
那当我们有两个定制异常处理类都对定制类有特殊处理,这个时候SpringMVC会怎么选择呢,如下示例,两个异常处理器都都指定了生效范围为TestController,这个时候TestController触发异常时,经测试都是走Yuqi2这个处理器,debug源码看到,因为在遍历ControllerAdviceBean时,Yuqi2在Yuqi3的位置上面,所以遍历的时候直接就在Yuqi2结束处理了,我们可以在处理器中手动加@Order注解,指定它们的加载属性,例如Yuqi2上加@Order(2),Yuqi3上加@Order(1),这样Yuqi3的加载顺序就优先,再次触发异常时,就会走Yuqi3的处理。
@RestControllerAdvice(assignableTypes = {TestController.class}) public class Yuqi2ExceptionHandler { @ExceptionHandler(ArithmeticException.class) public String handleArithmeticException(ArithmeticException e) { return "handle ArithmeticException v2"; } } @RestControllerAdvice(assignableTypes = {TestController.class}) public class Yuqi3ExceptionHandler { @ExceptionHandler(ArithmeticException.class) public String handleArithmeticException(ArithmeticException e) { return "handle ArithmeticException v3"; } }
5. 最后
相信跟着流程看了一遍源码后,大家对@ControllerAdvice和@ExceptionHandler的执行原理有了更加清晰的认识,通过源码重新过了一遍同时也是我自己学习了一遍,由于笔者水平有限,文中可能有一些不对的地方,希望大家能够指出,共同进步。