SpringMVC异常解析器处理异常DispatcherServlet#processHandlerException原理

catc

h (Exception ex) { dispatchException = ex;} catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);} org.springframework.web.servlet.DispatcherServlet#processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException){ // 其他步骤我们在运行原理的时候说明过了,详见SpringMVC处理请求整个链路原理 if (exception != null) { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); // 处理异常 mv = processHandlerException(request, response, handler, exception);{ // 如果存在异常解析器 if (this.handlerExceptionResolvers != null) { // 遍历所有的异常解析器解析异常,当前只有一个org.springframework.web.servlet.handler.HandlerExceptionResolverComposite for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { // 使用异常解析器解析异常 ModelAndView exMv = resolver.resolveException(request, response, handler, ex);{ // HandlerExceptionResolverComposite内部包含了三个HandlerExceptionResolver,分别是 // org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver // org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver // org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver if (this.resolvers != null) { for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) { // 使用异常解析器解析异常,这里才是真正处理异常的逻辑 ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);{ // 当前异常解析器是否可以解析,默认都是为空的,最终都是返回true if (shouldApplyTo(request, handler){ if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; handler = handlerMethod.getBean(); return super.shouldApplyTo(request, handler);{ return !hasHandlerMappings();{ // 默认都是为空的,结果都是返回false,上面加了!,所以返回的就是true return (this.mappedHandlers != null || this.mappedHandlerClasses != null); } } } }) { // 预处理响应体 prepareResponse(ex, response); // 处理异常,核心逻辑 ModelAndView result = doResolveException(request, response, handler, ex);{ HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null); return doResolveHandlerMethodException(request, response, handlerMethod, ex);{ // 获取处理异常的HandlerMethod ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);{ if (handlerMethod != null) { // 获取bean的类型,因为HanderMethod有两种,一种是正常的HandlerMethod,一种是异常情况下,ExceptionHander下方法对应的HandlerMethod // 也就是一个是@ControllerAdvice中的方法和@Controller中的方法 Class<?> handlerType = handlerMethod.getBeanType(); // 看一下是否被缓存过,因为当前正常的HanderMethod处理时,第一次出异常的情况下,肯定是没有的 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { // 创建一个新的异常解析器,用于解析当前这个正常的HandlerMethod的异常 // 这里和异常初始化的标注了@ControllerAdvice的Bean中的@ExceptionHandler方法的逻辑一样 // 详见: SpringMVC异常解析器初始化原理 // 先找Bean中的@ExceptionHandler,然后使用Key=处理的异常类型,Value=@ExceptionHandler标注的方法对象缓存下来 resolver = new ExceptionHandlerMethodResolver(handlerType); // 这里将创建过的这个异常解析器,也缓存起来,相当于给这个Controller缓存了一个异常解析器 // 下次这个Bean出现相同异常就可以直接使用这个异常处理器 Controller->ExceptionHandlerMethodResolver的缓存 this.exceptionHandlerCache.put(handlerType, resolver); } // 根据异常信息找到能处理这个异常的Method方法 // 有两种情况,一种是@ControllerAdvice,一种是@Controller // 如果是@Controller,那么上面 new ExceptionHandlerMethodResolver(handlerType)中是没有可以处理异常的方法 // 当没有可以处理当前异常的方法,默认method返回一个固定的方法对象NO_MATCHING_EXCEPTION_HANDLER_METHOD // 如果是@ControllerAdvice,那么,new ExceptionHandlerMethodResolver(handlerType)会找到ControllerAdvice标注了@ExceptionHandler的注解 // 然后保存下异常信息 -> 能处理这个异常信息方法的映射关系 Map<Class<? extends Throwable>, Method> mappedMethods Method method = resolver.resolveMethod(exception);{ return resolveMethodByThrowable(exception);{ // 根据异常信息找能处理当前异常的方法 Method method = resolveMethodByExceptionType(exception.getClass());{ // Map<Class<? extends Throwable>, Method> exceptionLookupCache; // 先看一下之前有没有找过这种异常,如果有就不需要再次去找,直接拿来用 Method method = this.exceptionLookupCache.get(exceptionType); if (method == null) { // 没有缓存的话,resolver有两种情况 // 一种是@ControllerAdvice,一种是@Controller // 如果是@Controller,那么上面 new ExceptionHandlerMethodResolver(handlerType)中是没有可以处理异常的方法 // 当没有可以处理当前异常的方法,默认method返回一个固定的方法对象NO_MATCHING_EXCEPTION_HANDLER_METHOD // 如果是@ControllerAdvice,那么,new ExceptionHandlerMethodResolver(handlerType)会找到ControllerAdvice标注了@ExceptionHandler的注解 // 然后保存下异常信息 -> 能处理这个异常信息方法的映射关系 Map<Class<? extends Throwable>, Method> mappedMethods // 此时会根据异常作为参数,找到对应ExceptionHandler中标注了具体异常或者参数中有这个异常的方法 // 再具体的可以看异常解析器初始胡流程,如何给this.mappedMethods赋值的 method = getMappedMethod(exceptionType); // 缓存起来,下次出现异常就不需要再次去找了 this.exceptionLookupCache.put(exceptionType, method); } return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null); } } } // 这里能找到就表示当前handlerType是一个标注了@ControllerAdvice的Bean // 如果找到了可以处理这个异常的方法,封装成ServletInvocableHandlerMethod对象,也是一个HandlerMethod if (method != null) { // 拿到了可以处理异常的方法,那剩下的就和正常直接方法一样了 // 正常执行的目标方法也是org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle // 只不过过程中有一点不一样而已 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);{ // 其他的都是赋值,同时处理了相应状态 evaluateResponseStatus();{ // 获取方法上的ResponseStatus注解 ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class); if (annotation == null) { // 查看类中有没有标注ResponseStatus注解 annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class); } // 如果存在ResponseStatus,保存状态码,还把异常原因保存 if (annotation != null) { this.responseStatus = annotation.code(); this.responseStatusReason = annotation.reason(); } } } } // 如果上面两种情况还没找到可以处理异常的方法 // 那就只能使用绝招了,遍历初始化的时候,缓存的所有标注了@ControllerAdvice的Bean对象 for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { ControllerAdviceBean advice = entry.getKey(); if (advice.isApplicableToBeanType(handlerType) ControllerAdvice controllerAdvice = (beanType != null ? AnnotatedElementUtils.findMergedAnnotation(beanType, ControllerAdvice.class) : null); // 然后校验@ControllerAdvice中的属性当前Controller是否符合,如果符合才能处理 ){ // 获取这个adviceBean对应的resolver,在初始化的时候已经保存了 Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache // 并且所有adviceBean的resolver都是equals == true的 ExceptionHandlerMethodResolver resolver = entry.getValue(); // 使用对应的异常解析器去解析此异常 Method method = resolver.resolveMethod(exception); // 遍历所有的adviceBean,直到找到为止 if (method != null) { // 返回HandlerMethod,上面有一样的逻辑 // 拿到了可以处理异常的方法,那剩下的就和正常直接方法一样了 // 正常执行的目标方法也是org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle // 只不过过程中有一点不一样而已 // ServletInvocableHandlerMethod内部实现上面说明了 return new ServletInvocableHandlerMethod(advice.resolveBean(), method); } } } } } // 存在参数解析器,将参数解析器给新创建的HandlerMethod if (this.argumentResolvers != null) { exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } // 设置返回结果的处理器 if (this.returnValueHandlers != null) { exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } // 构建一个自己的Request对象,持有req,resp,可以对session,attribute处理 ServletWebRequest webRequest = new ServletWebRequest(request, response); // 构建一个执行容器,执行过程中的数据都保存到这个容器中,例如,视图,Model数据,状态码,请求是否处理完成等等 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // 保存异常信息,因为异常内部可以还设置了异常,例如Throwable cause = ex.getCause(); ArrayList<Throwable> exceptions = new ArrayList<>(); try { // 找所有的异常信息 Throwable exToExpose = exception; while (exToExpose != null) { exceptions.add(exToExpose); Throwable cause = exToExpose.getCause(); exToExpose = (cause != exToExpose ? cause : null); } // 创建参数的数组,+1是除了异常参数外,还要设置HandleMethod为最后一个参数 Object[] arguments = new Object[exceptions.size() + 1]; exceptions.toArray(arguments); arguments[arguments.length - 1] = handlerMethod; // 执行业务逻辑,详见执行目标方法-ServletInvocableHandlerMethod#invokeAndHandle原理 exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } catch (Throwable invocationEx) { // 如果出现异常,返回空 return null; } // 如果此请求结束了,返回一个空的ModelAndView对象 // 这个标志是在invokeAndHandle设置的 if (mavContainer.isRequestHandled()) { return new ModelAndView(); } // 请求没有结束 else { // 获取model数据 ModelMap model = mavContainer.getModel(); // 获取状态码 HttpStatus status = mavContainer.getStatus(); // 创建一个新的modelAndview,并包运行过程中的viewName,model数据,状态码给它 ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); // 其实这个在构造方法中已经赋值了,源码没有必要 mav.setViewName(mavContainer.getViewName()); // 如果运行过程中,mavContainer中的view不是String类型,也就是不是一个viewName if (!mavContainer.isViewReference()) { // 将view的类型改为View对象 mav.setView((View) mavContainer.getView()); } // 如果这个model为一个重定向的model if (model instanceof RedirectAttributes) { // 在Spring MVC中,下面代码是用来获取重定向时传递的Flash属性的代码,Flash属性是一种在重定向期间传递数据的机制 // 当使用`RedirectAttributes`对象在控制器方法中执行重定向时,可以使用`addFlashAttribute`方法将属性添加到Flash范围中 // 然后,重定向后的目标控制器中可以通过`getFlashAttributes`方法获取这些属性,并在视图中使用。 // 这种机制特别适合需要在重定向期间传递数据但又不希望数据暴露在URL中的情况。 Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } } // 返回结果,就是ModelAndView return result; } } if (mav != null) { return mav; } } } } if (exMv != null) { break; } } } } errorView = (mv != null); } }