引言
前一篇文章 【SpringMVC 篇(一)DispatcherServlet 初始化】 中介绍了刷新的触发、基础组建的加载以及以 HandlerMapping 为例讲解了一下 url 与 controller 的映射。那么这篇文章将继续介绍 DispatcherServlet 关于请求处理的部分。
请求
在开始介绍 DispatcherServlet 处理请求之前,我们回忆一下原始的 servlet 是如何处理请求的。
Servlet
1
| public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
|
servlet 是一个接口,该接口中的 service 方法可以说是核心方法,所有的请求都会由该方法进行调度。根据请求的方法来引导你到 doXXX 系列的方法。而 doXXX 系列的方法并没有在该接口中进行声明,而是放到了它的子类 HttpServlet 中。而 DispatcherServlet 也是一个 servlet,所以它也按照该流程处理请求。
时序图
我们先看一下请求是如何通过 HttpServlet 以及 FrameworkServlet 中转到 DispatcherServlet 的。
先对请求进行分析:
- 请求进入 HttpServlet 的 service(ServletRequest, ServletResponse) 方法,该方法将 ServletRequest 、ServletResponse 转化为 HttpServletRequest、HttpServletResponse。
- HttpServlet 调用 FrameworkServlet 的 service(ServletRequest, ServletResponse) 方法。该方法对请求的 Http 方法进行判断,然后调用 HttpServlet 的 service(ServletRequest, ServletResponse) 方法。
- HttpServlet 的 service(ServletRequest, ServletResponse) 方法根据请求调用 FrameworkServlet 的 doXXX 系列方法。
- FrameworkServlet 的 doXXX 方法调用 processRequest 方法,该方法将请求转到 DispatcherServlet 的 doService() 方法。
- DispatcherServlet 的 doService() 方法调用 doDispatcher() 方法。
过程比较绕,但是最终调用的其实就是 DispatcherServlet 的 doDispatch() 方法了,不过在介绍它之前,我想有必要从 FrameworkServlet 的 processRequest() 方法开始。看它有没有对请求对象做处理。
FrameworkServlet
DispatcherServlet 的直接父类。
FrameworkServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis(); Throwable failureCause = null; LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); initContextHolders(request, localeContext, requestAttributes);
try { doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); }
finally { resetContextHolders(request, previousLocaleContext, previousAttributes); if (requestAttributes != null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); publishRequestHandledEvent(request, response, startTime, failureCause); } }
|
这个方法主要的作用就是获取之前设置的 context 对象,然后为当前 request 建立 ContextHolder (ThreadLocal)对象,并在请求完毕之后将其恢复到之前获取的 context 对象(如果为空就直接清除),同时发布处理事件。这里有个疑问,就是既然先获取之前的 context 对象,那么肯定有个入口会设置该值,那么是什么时候设置的呢?而且,请求之后必须要清空 ContextHolder ,那么这里并没有看到相关的清空操作。那么 Spring 是怎么做的呢?大家可以思考一下。
我们继续分析,打开 DispatcherServlet 的 doService() 方法。
doService
doService 是一个覆盖的方法,供父类调用,而该方法只是做一下请求的准备工作,然后将具体请求处理交由 doDispatch 方法。
DispatcherServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request); 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)); } } }
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()) { if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request); } }
|
doDispatch
doDispatch 是处理请求的核心方法
DispatcherServlet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| 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);
mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 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; }
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) { 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()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
|
相关的说明已经详细的在代码中标注了。大体上我们可以总结如下:
- 每个请求都会赋予一个异步管理器。
- 拦截器 preHandle() 方法的调用。
- 真实请求的调用。
- 如果是异步请求,那么拦截器的 postHandle() 和 afterCompletion() 方法是不会生效的,这里需要使用 AsyncHandlerInterceptor 拦截器。
RequestContextFilter
还记得我们之前提到的就是 ContextHolder 是什么时候进行属性配置的吗?其实在请求进来的时候,先经过一轮轮的 Filter 处理,其中之一的 RequestContextFilter 就是用来做这个工作的。我们看一下它的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request, response); initContextHolders(request, attributes);
try { filterChain.doFilter(request, response); } finally { resetContextHolders(); if (logger.isTraceEnabled()) { logger.trace("Cleared thread-bound request context: " + request); } attributes.requestCompleted(); } }
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) { LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable); RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); if (logger.isTraceEnabled()) { logger.trace("Bound request context to thread: " + request); } }
private void resetContextHolders() { LocaleContextHolder.resetLocaleContext(); RequestContextHolder.resetRequestAttributes(); }
|
代码很简单,无非就是请求之前初始化 ContextHolder,请求之后清空 ContextHolder。我们在项目中常用的 RequestContextHolder 是怎么来的,到这里我想大家已经豁然开朗了。
尾声
这篇文章我们介绍了请求的处理流程,把 DispatcherServlet 核心方法介绍了一遍。有人可能会提出疑问,请求的调用讲的并不是很详细,@RequestBody 以及 @ResponseBody 的处理都没提到。不要紧,因为接下来的系列文章 【SpringMVC 篇 消息转换器】 会对请求接下来的处理进行详细的介绍。