共计 7858 个字符,预计需要花费 20 分钟才能阅读完成。
后面松哥和大家聊了 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 办法比拟相熟了,当然这里还波及到很多组件,这些组件松哥将在前面的文章中和大家逐个进行剖析。