本篇将分析一次请求从接收处处理的最终环节——视图处理,也是 SpringMVC 源码解析的最后一节。将涉及异常处理和视图转发两部分。java
承接上篇,来看 “processDispatchResult” 的实现。web
public class DispatcherServlet extends FrameworkServlet { private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, 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 (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { return; } if (mappedHandler != null) { // 调用拦截器的 afterCompletion mappedHandler.triggerAfterCompletion(request, response, null); } } }
总体来看,该方法有三个主要步骤:异常处理、视图转发、拦截器调用。其中拦截器调用逻辑简单,就是遍历调用拦截器的 afterCompletion 方法。因此重点来看前二者的实现。数组
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ModelAndView exMv = null; // 遍历全部注册的 HandlerExceptionResolver(按优先级),调用其 resolveException // 直到有一个解析器处理后返回非空的 ModelAndView for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } // 若是 exMv != null,说明被解析器捕获并处理 if (exMv != null) { if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // 若是被处理返回的 ModelAndView未指定页面,则使用默认页面 if (!exMv.hasView()) { exMv.setViewName(getDefaultViewName(request)); } ...// 省略日志 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); // 返回异常处理视图 return exMv; } // 没有被捕获的就会抛出异常 throw ex; }
这里的 HandlerExceptionResolver 就是 Dispatcher 初始化策略中,initHandlerExceptionResolvers 方法中注册的,默认会扫描注册全局 HandlerExceptionResolver 类型的实例。先来回顾下 SpringMVC 处理异常的几种方式:缓存
方式一可能比较局限,一个类只能针对某种类型异常的处理;方式2、方式三比较方便,共同点都是借助了 @ExceptionHandler 注解,value 能够指定异常类型数组;方式四通常用于异常是自定义的场景;方式五是配置方式的,能够经过基于 xml 文件配置,也能够经过 SpringBoot 的基于代码配置。并发
这里仅分析下来方式二、方式三的实现原理,这也是比较经常使用的两种方式:app
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 判断是否支持该 Handler的异常处理 if (shouldApplyTo(request, handler)) { if (this.logger.isDebugEnabled()) { this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex); } // 是否避免返回的页面被缓存,默认为 false // 避免缓存是经过添加“Cache-Control=no-store”实现的 prepareResponse(ex, response); // 该方法交由子类扩展 ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { // 日志记录 logException(ex, request); } return result; } else { return null; } } protected boolean shouldApplyTo(HttpServletRequest request, Object handler) { if (handler != null) { // 首先判断 mappedHandlers是否包含此 Handler,由 setMappedHandlers方法指定 if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) { return true; } // 再判断 mappedHandlerClasses是否指定了该 Handler类型,由 setMappedHandlerClasses方法指定 if (this.mappedHandlerClasses != null) { for (Class<?> handlerClass : this.mappedHandlerClasses) { if (handlerClass.isInstance(handler)) { return true; } } } } // 若是不指定,默认 mappedHandlers和 mappedHandlerClasses都为 null,所以返回 true return (this.mappedHandlers == null && this.mappedHandlerClasses == null); } }
AbstractHandlerExceptionResolver 是框架层面全部异常解析器的公共抽象父类,实现了resolveException 方法,首先判断下是否支持该 Handler 的异常处理,若是不指定 mappedHandlers、mappedHandlerClasses,默认支持所有 Handler 的异常处理。框架
具体异常处理的逻辑以抽象方法(doResolveException)的形式交由子类实现。jsp
@ExceptionHandler 本来的支持类为 AnnotationMethodHandlerExceptionResolver,该类在 Spring 3.2 版本后声明废弃,代替者为 ExceptionHandlerExceptionResolver。ide
AbstractHandlerMethodExceptionResolver 做为 ExceptionHandlerExceptionResolver 的抽象父类,实现 doResolveException。this
public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver { @Override protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 抽象方法:子类实现 return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex); } }
这个方法这是作了次参数的向下转型,进而调用 doResolveHandlerMethodException ,这个方法由 ExceptionHandlerExceptionResolver 实现。(这里又看到了 “HandlerMethod ” 的身影)
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { @Override protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) { // 跟请求处理相同的套路,一样使用 ServletInvocableHandlerMethod ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } // 入参和返回解析器设置 exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); try { if (logger.isDebugEnabled()) { logger.debug("Invoking @ExceptionHandler method: " + 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) { // 有可能在调用 Handler处理时抛出异常,例如断言判空之类的 if (invocationEx != exception && logger.isWarnEnabled()) { logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx); } return null; } // 判断请求是否已在 Handler中彻底处理 if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { // 将 Handler处理的结果封装成 ModelAndView返回 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(); request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } }
刚才提到的异常处理方式2、方式三,说到底也是一次 Method 的调用过程:就像请求经过映射关系找到对应的 HandlerMethod 同样,ExceptionHandlerExceptionResolver 经过定义的异常类型来找到对应的 HandlerMethod,进而调用 invokeAndHandle 来反射调用(里面包含的参数解析、反射调用等,在以前的章节已分析过)。
这里重点来分析下 getExceptionHandlerMethod 寻找 HandlerMethod 的逻辑。
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { // 首先获取抛出异常点(业务处理方法)所在类的类型 Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); if (handlerMethod != null) { // 先从缓存中取,若是没有则建立一个并放入缓存 ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } // 关注此方法:解析出处理该异常的方法 Method method = resolver.resolveMethod(exception); // 若是不为 null,说明抛异常的方法所在类下,正好有一个被 @ExceptionHandler注解标识的方法对应处理该异常 if (method != null) { // 包装一个 ServletInvocableHandlerMethod返回 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); } } // 若是对应类下没有能力捕获到该异常,就遍历全部 @ControllerAdvice标识的类下,指定的 @ExceptionHandler for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { // 判断是否支持传入类型的加强 // 若是 @ControllerAdvice没有指定 basePackages、basePackageClasses、annotations、assignableTypes,则返回 true,即全局加强 // 若是指定了上述的顺序,逐个筛选,知足其一就返回 true if (entry.getKey().isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); // 关注此方法:解析出处理该异常的方法 Method method = resolver.resolveMethod(exception); if (method != null) { // 包装一个 ServletInvocableHandlerMethod返回 return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); } } } return null; }
从源码能够看出,首先会从异常抛出所在类中找处理方法(方式二),没有才会到全局加强中找(方式三)。这里使用到的缓存 exceptionHandlerAdviceCache 初始化借助了生命周期接口实现 InitializingBean .afterPropertiesSet,该方法调用了 initExceptionHandlerAdviceCache 将全局被 @ControllerAdvice 标识的类实例放入 exceptionHandlerAdviceCache。
接着来看下二者共同使用的 ExceptionHandlerMethodResolver 是如何经过异常解析出 Method,上面代码经过 new ExceptionHandlerMethodResolver(handlerType) 建立了该类的实例,该构造器将会初始化异常和处理方法的映射关系,源码以下:
public class ExceptionHandlerMethodResolver { // 存放异常和处理方法的映射关系 private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap<Class<? extends Throwable>, Method>(16); public ExceptionHandlerMethodResolver(Class<?> handlerType) { // 遍历 Handler下的全部方法,找到被 @ExceptionHandler标识的方法 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { // 遍历方法指定处理的异常类型(Throwable类型) for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { // 注册异常和对应处理方法的映射关系 addExceptionMapping(exceptionType, method); } } } private List<Class<? extends Throwable>> detectExceptionMappings(Method method) { List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>(); // 找方法上 @ExceptionHandler 注解声明的异常类型 detectAnnotationExceptionMappings(method, result); // 注解未指定,则会将方法入参为 Throwable类型填充 result if (result.isEmpty()) { for (Class<?> paramType : method.getParameterTypes()) { if (Throwable.class.isAssignableFrom(paramType)) { result.add((Class<? extends Throwable>) paramType); } } } // 若是注解和方法都没有指定异常类型,抛出异常 if (result.isEmpty()) { throw new IllegalStateException("No exception types mapped to " + method); } return result; } private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) { // 关联关系 Method oldMethod = this.mappedMethods.put(exceptionType, method); // 防止一个异常对应多个处理方法 if (oldMethod != null && !oldMethod.equals(method)) { throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}"); } } }
须要注意,这里的 detectExceptionMappings 若是注解指定了异常类型,就不会再考虑方法入参的异常类型了。
映射关系创建以后,继续看解析逻辑:
public Method resolveMethod(Exception exception) { Method method = resolveMethodByExceptionType(exception.getClass()); if (method == null) { Throwable cause = exception.getCause(); if (cause != null) { method = resolveMethodByExceptionType(cause.getClass()); } } return method; } public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) { // 首先从缓存中获取 Method method = this.exceptionLookupCache.get(exceptionType); if (method == null) { // 缓存中没有,则从 mappedMethods中获取 method = getMappedMethod(exceptionType); // 放入缓存 this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND)); } return (method != NO_METHOD_FOUND ? method : null); }
这里可能有人会疑问,以前已经创建了异常和处理方法的映射关系,为何又维护了一个 exceptionLookupCache 呢?
咱们来假象一种场景:某方法抛出的异常 A,A 继承自类型 B。咱们针对异常 A 和 B 分别定义了处理方法,那么究竟哪一个方法会处理异常 A?
所以就有一个筛选逻辑,逻辑见 getMappedMethod 方法,使用 ExceptionDepthComparator 比较器进行最优处理方法挑选(即挑选声明异常越具体的方法)。这里不作展开分析。
到此为止,异常处理已经分析完成。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // 使用 initLocaleResolver初始化的 LocaleResolver进行国际化处理(默认:AcceptHeaderLocaleResolver) // 肯定请求的语言环境并将其应用于响应 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); View view; String viewName = mv.getViewName(); if (viewName != null) { // 遍历注册的 ViewResolver,调用resolveViewName解析 viewName为 View view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } else { // 若是没有指定 viewName,则直接获取视图 view = mv.getView(); if (view == null) { throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } ....// 省略日志 try { if (mv.getStatus() != null) { // 响应码设置 response.setStatus(mv.getStatus().value()); } // 调用 View.render view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { ....// 省略日志 throw ex; } }
涉及到了国际化处理(LocaleResolver),视图解析(ViewResolver)。其中 resolveViewName 遍历了全部注册的 ViewResolver,将 viewName 转换为 View。以 UrlBasedViewResolver 为例,它会将指定的 prefix、suffix 先后缀与 viewName 拼接后建立 View。
public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware { @Override public void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ....// 省略日志 // 将 View实例配置中的固定属性和 Model中的动态属性合并到一块儿 Map<String, Object> mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); // 子类扩展 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); } }
renderMergedOutputModel 方法由子类实现。
public class InternalResourceView extends AbstractUrlBasedView { @Override protected void renderMergedOutputModel( Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { // 调用 setAttribute将 model中属性设置到 request中 exposeModelAsRequestAttributes(model, request); exposeHelpers(request); // 肯定请求分派器的路径 String dispatcherPath = prepareForRendering(request, response); // 获取请求的 RequestDispatcher RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); if (rd == null) { throw new ServletException("Could not get RequestDispatcher for [" + getUrl() + "]: Check that the corresponding file exists within your web application archive!"); } // 判断当前 response是否已经提交 if (useInclude(request, response)) { response.setContentType(getContentType()); ....// 省略日志 // 调用 RequestDispatcher.include,见 <jsp:include> // 该方法调用后,原 Servlet仍对请求有主导权,并将新的资源包含到当前响应当中 rd.include(request, response); } else { ....// 省略日志 // 调用 RequestDispatcher.forward // 该方法将请求直接转发给其余 Servlet处理 rd.forward(request, response); } } }
这里以 InternalResourceView 为例,JSP 就是经过这种方式实现的。JSP 全称 Java Servlet Page,底层就是将所写的 JSP 页面编译成 Servlet 字节码文件,这里的转发也就是将请求转发给这些对应的 Servlet 。
固然除了 InternalResourceView ,还有像 MappingJackson2JsonView(支持 @JsonView 注解)等。以后的逻辑就交由 Servlet 容器(像 Tomcat)的实现了。
到此为止,Spring 从容器启动到一次请求处理就所有解析完毕了。