后面松哥和大家聊了 DispatcherServlet 的父类 FrameworkServlet,大家从中理解到在 DispatcherServlet 中,办法执行的入口应该是 doService。如果小伙伴们还没看后面的剖析,能够先看下,这有助于了解本文,传送门SpringMVC 源码剖析之 FrameworkServlet。
即便你没看过 DispatcherServlet 的源码,预计也据说过:DispatcherServlet 是 SpringMVC 的大脑,它负责整个 SpringMVC 的调度工作,是 SpringMVC 中最最外围的类,SpringMVC 整个顶层架构设计都体现在这里,所以搞明确 DispatcherServlet 的源码,基本上 SpringMVC 的工作原理也就了然于胸了。
通过上篇文章的剖析,大家曾经晓得 DispatcherServlet 的入口办法是 doService,所以明天咱们就从 doService 办法开始看起,松哥将率领大家,一步一步揭开 DispatcherServlet 的面纱。
doService
先来看 doService,把源码先贴上来,而后咱们逐渐剖析:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<?> attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); } } } // Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); if (this.flashMapManager != null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); } RequestPath previousRequestPath = null; if (this.parseRequestPath) { previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE); ServletRequestPathUtils.parseAndCache(request); } try { doDispatch(request, response); } finally { if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); }}
这里的代码并不长,咱们来略微剖析一下:
- 首先判断以后申请是不是 include 申请,如果是 include,则对 request 的 attribute 做一个快照备份,在最初的 finally 中再对备份的属性进行还原。
- 接下来对 request 设置一些常见属性,例如利用上下文、国际化的解析器、主题解析器等等,这些货色在初始化的时候曾经筹备好了,这里只是利用(初始化过程参见SpringMVC 初始化流程剖析一文)。
- 接下来解决 flashMap,如果存在 flashMap 则进行还原,这一块松哥在之前的文章中和小伙伴们曾经分享过了,传送门SpringMVC 中的参数还能这么传递?涨姿态了!。
- 接下来解决 RequestPath,将申请门路对象化以备后续应用(在前面的申请映射匹配时会用到)。
- 调用 doDispatch 办法进行下一步解决。
- 还原快照属性、还原 RequestPath。
所以说这段代码并不难理解,它的外围在于 doDispatch 办法,所以接下来咱们就来看看 doDispatch 办法。
doDispatch
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); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. 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; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. 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); } } }}
这个办法比拟长,波及到很多组件的解决,这里松哥先和大家把思路梳理畅通,各个组件的具体用法松哥将在当前的文章中和大家认真分享。
doDispatch 办法其实次要做了两方面的事件:申请解决以及页面渲染,咱们先来看看初始变量的含意:
- processedRequest:这个用来保留实际上所用的 request 对象,在前面的流程中会对以后 request 对象进行查看,如果是文件上传申请,则会对申请从新进行封装,如果不是文件上传申请,则持续应用原来的申请。
- mappedHandler:这是具体解决申请的处理器链,处理器链蕴含两方面的货色:申请处理器和对应的 Interceptor。
- multipartRequestParsed:示意是否是文件上传申请的标记。
- asyncManager:这是一个异步申请管理器。
- mv:这是最终渲染返回的 ModelAndView 对象。
- dispatchException:示意申请处理过程中所抛出的异样,这个异样不包含渲染过程抛出的异样。
接下来再来看看具体的解决逻辑:
- 首先通过 checkMultipart 查看是不是文件上传申请,如果是,则对以后 request 从新进行包装,如果不是,则间接将参数返回。
- 如果 processedRequest 不等于 request,则阐明以后申请是文件上传申请(request 在 checkMultipart 办法中被从新封装了),否则阐明以后申请不是文件上传申请。
- 依据以后申请,调用 getHandler 办法获取申请处理器,如果没找到对应的申请处理器,则调用 noHandlerFound 办法抛出异样或者给出 404。
- 接下来再调用 getHandlerAdapter 办法,依据以后的处理器找到处理器适配器。
- 而后解决 GET 和 HEAD 申请头的 Last_Modified 字段。当浏览器第一次发动 GET 或者 HEAD 申请时,申请的响应头中蕴含一个 Last-Modified 字段,这个字段示意该资源最初一次批改工夫,当前浏览器再次发送 GET、HEAD 申请时,都会携带上该字段,服务端收到该字段之后,和资源的最初一次批改工夫进行比照,如果资源还没有过期,则间接返回 304 通知浏览器之前的资源还是能够持续用的,如果资源曾经过期,则服务端会返回新的资源以及新的 Last-Modified。
- 接下来调用拦截器的 preHandle 办法,如果该办法返回 false,则间接 return 掉以后申请(拦截器的用法大家能够参考松哥之前录的收费的 SpringMVC 视频教程,里边有讲,传送门硬核!松哥又整了一套免费视频,搞起!)。
- 接下来执行
ha.handle
去调用真正的申请,获取到返回后果 mv。 - 接下来判断以后申请是否须要异步解决,如果须要,则间接 return 掉。
- 如果不须要异步解决,则执行 applyDefaultViewName 办法,查看以后 mv 是否没有视图,如果没有(例如办法返回值为 void),则给一个默认的视图名。
- 接下来调用 applyPostHandle 办法执行拦截器里边的 postHandle 办法。
- 接下来调用 processDispatchResult 办法对执行后果进行解决,包含异样解决、渲染页面以及执行拦截器的 afterCompletion 办法都在这里实现。
- 最初在 finally 代码块中判断是否开启了异步解决,如果开启了,则调用相应的拦截器;如果申请是文件上传申请,则再调用 cleanupMultipart 办法革除文件上传过程产生的一些临时文件。
这是 doDispatch 办法的一个大抵执行逻辑,doDispatch 里边的 try-catch 有两层,最里边那一层,抛出来的异样会被赋值给 dispatchException 变量,这些异样最终在 processDispatchResult 办法中被解决掉,里面的异样则是 processDispatchResult 办法在执行的过程中抛出的异样,一般来说次要是页面渲染时候的异样。
processDispatchResult
最初咱们再来看下 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) { mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); 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 (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); }}
能够看到,在 processDispatchResult 办法中首先对异样进行了解决,配置好异样对应的 ModelAndView,而后调用 render 办法对页面进行渲染,最初通过 triggerAfterCompletion 办法去触发拦截器的 afterCompletion 办法。
小结
至此,咱们就把一个申请的大抵流程和大家梳理完了,松哥画了一张流程图咱们一起来看下:
这下置信大家对 doDispatch 办法比拟相熟了,当然这里还波及到很多组件,这些组件松哥将在前面的文章中和大家逐个进行剖析。