SpringMVC 源码分析之 DispatcherServlet
- FrameworkServlet
- service
- processRequest
- LocaleContext 和 RequestAttributes
- LocaleContext
- RequestAttributes
- 事件发布
- DispatcherServlet
- doService
- 代码分析
- doDispatch
- 参数含义
- 具体的处理逻辑:
- processDispatchResult
- 引用
SpringMVC 的核心是 DispatcherServlet,和所有的 Servlet 一样,DispatcherServlet 对请求的处理也是从 service 方法开始,而 DispatcherServlet 的 service 方法在父类 FrameworkServlet 中,因此我们先来看看 FrameworkServlet,这有助于我们理解 DispatcherServlet。
FrameworkServlet
FrameworkServlet 继承自 HttpServletBean,而 HttpServletBean 继承自 HttpServlet,HttpServlet 就是 JavaEE 里边的东西了,这里我们不做讨论,从 HttpServletBean 开始就是框架的东西了,但是 HttpServletBean 比较特殊,它的特殊在于它没有进行任何的请求处理,只是参与了一些初始化的操作,这些比较简单,所以这里我们对 HttpServletBean 不做分析,就直接从它的子类 FrameworkServlet 开始看起。
service
FrameworkServlet#service 源码如下:
/*** Override the parent class implementation in order to intercept PATCH requests.*/@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod == HttpMethod.PATCH || httpMethod == null) {processRequest(request, response);}else {super.service(request, response);}}
可以看到,在该方法中,首先获取到当前请求方法,然后对 patch 请求额外关照了下,其他类型的请求统统都是 super.service 进行处理。
然而在 HttpServlet 中并未对 doGet、doPost 等请求进行实质性处理,所以 FrameworkServlet 中还重写了各种请求对应的方法,如 doDelete、doGet、doOptions、doPost、doPut、doTrace 等,其实就是除了 doHead 之外的其他方法都重写了。
doDelete、doGet、doPost 以及 doPut 四个方法:
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}
可以看到,这里又把请求交给 processRequest 去处理了,在 processRequest 方法中则会进一步调用到 doService,对不同类型的请求分类处理。
doOptions 和 doTrace 则稍微有些差异,如下:
@Override
protected void doOptions(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {processRequest(request, response);if (response.containsHeader("Allow")) {return;}}super.doOptions(request, new HttpServletResponseWrapper(response) {@Overridepublic void setHeader(String name, String value) {if ("Allow".equals(name)) {value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();}super.setHeader(name, value);}});
}
@Override
protected void doTrace(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (this.dispatchTraceRequest) {processRequest(request, response);if ("message/http".equals(response.getContentType())) {return;}}super.doTrace(request, response);
}
可以看到这两个方法的处理多了一层逻辑,就是去选择是在当前方法中处理对应的请求还是交给父类去处理,由于 dispatchOptionsRequest 和 dispatchTraceRequest 变量默认都是 false,因此默认情况下,这两种类型的请求都是交给了父类去处理。
processRequest
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);}}
这个方法虽然比较长,但是其实它的核心就是最中间的 doService 方法,以 doService 为界,我们可以将该方法的内容分为三部分:
- doService 之前主要是一些准备工作,准备工作主要干了两件事,第一件事就是从 LocaleContextHolder 和 RequestContextHolder 中分别获取它们原来保存的 LocaleContext 和 RequestAttributes 对象存起来,然后分别调用 buildLocaleContext 和 buildRequestAttributes 方法获取到当前请求的 LocaleContext 和 RequestAttributes 对象,再通过 initContextHolders 方法将当前请求的 LocaleContext 和 RequestAttributes 对象分别设置到 LocaleContextHolder 和 RequestContextHolder 对象中;第二件事则是获取到异步管理器并设置拦截器。
- 接下来就是 doService 方法,这是一个抽象方法,具体的实现在 DispatcherServlet 中,这个后边放到 DispatcherServlet 中再分析。
- 第三部分就是 finally 中,这个里边干了两件事:第一件事就是将 LocaleContextHolder 和 RequestContextHolder 中对应的对象恢复成原来的样子(参考第一步);第二件事就是通过 publishRequestHandledEvent 方法发布一个 ServletRequestHandledEvent 类型的消息。
processRequest 其实主要做了两件事,第一件事就是对 LocaleContext 和 RequestAttributes 的处理,第二件事就是发布事件。我们对这两件事分别来研究。
LocaleContext 和 RequestAttributes
LocaleContext 和 RequestAttributes 都是接口,不同的是里边存放的对象不同。
LocaleContext
LocaleContext 里边存放着 Locale,也就是本地化信息,如果我们需要支持国际化,就会用到 Locale。 SpringMVC 中还给我们提供了 LocaleContextHolder,这个工具就是用来保存当前请求的 LocaleContext 的。
RequestAttributes
RequestAttributes 是一个接口,这个接口可以用来 get/set/remove 某一个属性。
RequestAttributes 有诸多实现类,默认使用的是 ServletRequestAttributes,通过 ServletRequestAttributes,我们可以 getRequest、getResponse 以及 getSession。和 LocaleContext 类似,RequestAttributes 被保存在 RequestContextHolder 中,RequestContextHolder 的原理也和 SecurityContextHolder 类似。
事件发布
processRequest 方法中的事件发布,在 finally 代码块中会调用 publishRequestHandledEvent 方法发送一个 ServletRequestHandledEvent 类型的事件。正常情况下,这个事件总是会被发送出去,如果项目有需要,我们可以监听该事件【如此监听每一个请求处理时间等】。
DispatcherServlet
DispatcherServlet 是 SpringMVC 的大脑,它负责整个 SpringMVC 的调度工作,是 SpringMVC 中最最核心的类,SpringMVC 整个顶层架构设计都体现在这里,所以搞明白 DispatcherServlet 的源码,基本上 SpringMVC 的工作原理也就了然于胸了。
经过上边的分析,可以看到 DispatcherServlet 的入口方法是 doService,接下来就从 doService 方法开始看起,一步一步揭开 DispatcherServlet 的面纱。
doService
先来看 doService,源码如下:
@Overrideprotected 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);}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);}}}}
代码分析
- 打印日志【debug 及 trace 级别(日志信息包含 是否是转发,接口路径,请求方式,请求头,URL路径参数及请求体参数)】
- 首先判断当前请求是不是 include 请求,如果是 include,则对 request 的 attribute 做一个快照备份,在最后的 finally 中再对备份的属性进行还原。
- 接下来对 request 设置一些常见属性,例如应用上下文、国际化的解析器、主题解析器等等,这些东西在初始化的时候已经准备好了,这里只是应用。
- 接下来处理 flashMap,如果存在 flashMap 则进行复原。
- 接下来处理 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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}
参数含义
doDispatch 方法其实主要做了两方面的事情:请求处理以及页面渲染,其中各个初始变量的含义如下:
- processedRequest:这个用来保存实际上所用的 request 对象,在后面的流程中【
checkMultipart(request)
】会对当前 request 对象进行检查,如果是文件上传请求,则会对请求重新进行封装,如果不是文件上传请求,则继续使用原来的请求。 - multipartRequestParsed:表示是否是文件上传请求的标记【上步处理结束,上传文件的请求会重新封装请求对象,故这里是通过检验 request 对象是否还是同一个对象,来标识是否是文件上传请求】。
- mappedHandler:这是具体处理请求的处理器链,处理器链包含两方面的东西:请求处理器和对应的 Interceptor。
- 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 掉当前请求
- 接下来执行 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) {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);}}// Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Concurrent handling started during a forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}}
在 processDispatchResult 方法中首先对异常进行了处理,配置好异常对应的 ModelAndView,然后调用 render 方法对页面进行渲染,最后通过 triggerAfterCompletion 方法去触发拦截器的 afterCompletion 方法。
引用
- SpringMVC 源码分析之 DispatcherServlet
- SpringMVC 源码分析之 FrameworkServlet