該系列文檔是本人在學習 Spring MVC 的源碼過程當中總結下來的,可能對讀者不太友好,請結合個人源碼註釋 Spring MVC 源碼分析 GitHub 地址 進行閱讀html
Spring 版本:5.2.4.RELEASEjava
該系列其餘文檔請查看:《精盡 Spring MVC 源碼分析 - 文章導讀》ios
在上一篇《WebApplicationContext 容器的初始化》文檔中分析了 Spring MVC 是如何建立兩個容器的,其中建立Root WebApplicationContext 後,調用其refresh()
方法會觸發刷新事件,完成 Spring IOC 初始化相關工做,會初始化各類 Spring Bean 到當前容器中,該系列文檔暫不分析git
咱們先來了解一個請求是如何被 Spring MVC 處理的,因爲整個流程涉及到的代碼很是多,因此本文的重點在於解析總體的流程,主要講解 DispatcherServlet 這個核心類,弄懂了這個流程後,才能更好的理解具體的源碼,回過頭再來看則會更加的豁然開朗github
Spring MVC 處理請求的流程大體如上圖所示web
DispatcherServlet
進行處理。DispatcherServlet
將該請求傳給了處理器映射組件 HandlerMapping
,並獲取到適合該請求的 HandlerExecutionChain 攔截器和處理器對象。DispatcherServlet
還不能直接調用處理器的邏輯,須要進行對處理器進行適配。處理器適配成功後,DispatcherServlet
經過處理器適配器 HandlerAdapter
調用處理器的邏輯,並獲取返回值 ModelAndView
對象。DispatcherServlet
須要根據 ModelAndView 解析視圖。解析視圖的工做由 ViewResolver
完成,若能解析成功,ViewResolver
會返回相應的 View 視圖對象。以上就是 Spring MVC 處理請求的全過程,上面的流程進行了必定的簡化,主要涉及到最核心的組件,還有許多其餘組件沒有表現出來,不過這並不影響你們對主過程的理解。spring
在上一篇《WebApplicationContext 容器的初始化》文檔講述 FramworkServlet 的 onRefresh 方法時,該方法由 DispatcherServlet
去實現,會初始化九大組件,如何初始化的這裏暫時不展開討論,默認會從 spring-webmvc
下面的 DispatcherServlet.properties
文件中讀取組件的實現類,感興趣能夠先閱讀一下源碼😈,後續會依次描述跨域
那麼接下來就簡單介紹一下 DispatcherServlet
和九大組件:數組
組件 | 說明 |
---|---|
DispatcherServlet | Spring MVC 的核心組件,是請求的入口,負責協調各個組件工做 |
MultipartResolver | 內容類型( Content-Type )爲 multipart/* 的請求的解析器,例如解析處理文件上傳的請求,便於獲取參數信息以及上傳的文件 |
HandlerMapping | 請求的處理器匹配器,負責爲請求找到合適的 HandlerExecutionChain 處理器執行鏈,包含處理器(handler )和攔截器們(interceptors ) |
HandlerAdapter | 處理器的適配器。由於處理器 handler 的類型是 Object 類型,須要有一個調用者來實現 handler 是怎麼被執行。Spring 中的處理器的實現多變,好比用戶處理器能夠實現 Controller 接口、HttpRequestHandler 接口,也能夠用 @RequestMapping 註解將方法做爲一個處理器等,這就致使 Spring MVC 沒法直接執行這個處理器。因此這裏須要一個處理器適配器,由它去執行處理器 |
HandlerExceptionResolver | 處理器異常解析器,將處理器( handler )執行時發生的異常,解析( 轉換 )成對應的 ModelAndView 結果 |
RequestToViewNameTranslator | 視圖名稱轉換器,用於解析出請求的默認視圖名 |
LocaleResolver | 本地化(國際化)解析器,提供國際化支持 |
ThemeResolver | 主題解析器,提供可設置應用總體樣式風格的支持 |
ViewResolver | 視圖解析器,根據視圖名和國際化,得到最終的視圖 View 對象 |
FlashMapManager | FlashMap 管理器,負責重定向時,保存參數至臨時存儲(默認 Session) |
Spring MVC 對各個組件的職責劃分的比較清晰。DispatcherServlet
負責協調,其餘組件則各自作份內之事,互不干擾。通過這樣的職責劃分,代碼會便於維護。同時對於源碼閱讀者來講,也會很友好。能夠下降理解源碼的難度,使你們可以快速理清主邏輯。這一點值得咱們學習。瀏覽器
ThemeResolver 和 FlashMapManager 組件在該系列文檔中不會進行講解,由於幾乎接觸不到,感興趣的能夠去 Google 一下,嘻嘻~😅 筆者沒接觸過
雖然在上面的總體流程圖中,咱們看到請求是直接被 DispatcherServlet 所處理,可是實際上,FrameworkServlet 纔是真正的入口,再來回顧一個 DispatcherServlet 的類圖,以下:
FrameworkServlet 覆蓋了 HttpServlet 的如下方法:
doGet(HttpServletRequest request, HttpServletResponse response)
doPost(HttpServletRequest request, HttpServletResponse response)
doPut(HttpServletRequest request, HttpServletResponse response)
doDelete(HttpServletRequest request, HttpServletResponse response)
doOptions(HttpServletRequest request, HttpServletResponse response)
doTrace(HttpServletRequest request, HttpServletResponse response)
service(HttpServletRequest request, HttpServletResponse response)
這些方法分別處理不一樣 HTTP 請求類型的請求,最終都會調用另外一個 processRequest(HttpServletRequest request, HttpServletResponse response)
方法
其中 doGet
、doPost
、doPut
和doDelete
四個方法是直接調用 processRequest
方法的
service(HttpServletRequest request, HttpServletResponse response)
方法,用於處理請求,方法以下:
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // <1> 得到請求方法 HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); // <2> 處理 PATCH 請求 if (httpMethod == HttpMethod.PATCH || httpMethod == null) { processRequest(request, response); } // <3> 處理其餘類型的請求 else { super.service(request, response); } }
得到 HttpMethod 請求方法
若請求方法是 PATCH
,調用 processRequest(HttpServletRequest request, HttpServletResponse response)
方法,處理請求。
由於 HttpServlet 默認沒提供處理 Patch
請求類型的請求 ,因此只能經過覆蓋父類的 service
方法來實現
若是是其餘類型的請求,則直接調用父類的 service
方法,該方法以下:
// HttpServlet.java protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // Note that this means NO servlet supports whatever method was requested, anywhere on this server. String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } }
可能你會有疑惑,爲何不在 service(HttpServletRequest request, HttpServletResponse response)
方法,直接調用 processRequest(HttpServletRequest request, HttpServletResponse response)
方法就行了?由於針對不一樣的請求方法,處理略微有所不一樣,父類 HttpServlet 已經處理了。
doOptions(HttpServletRequest request, HttpServletResponse response)
方法,用於處理 OPTIONS 類型的請求,方法以下:
@Override protected void doOptions(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 若是 dispatchOptionsRequest 爲 true ,則處理該請求,默認爲 true if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) { // 處理請求 processRequest(request, response); // 若是響應 Header 包含 "Allow" ,則不須要交給父方法處理 if (response.containsHeader("Allow")) { // Proper OPTIONS response coming from a handler - we're done. return; } } // Use response wrapper in order to always add PATCH to the allowed methods // 調用父方法,並在響應 Header 的 "Allow" 增長 PATCH 的值 super.doOptions(request, new HttpServletResponseWrapper(response) { @Override public void setHeader(String name, String value) { if ("Allow".equals(name)) { value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name(); } super.setHeader(name, value); } }); }
使用場景:AJAX 進行跨域請求時的預檢,須要向另一個域名的資源發送一個HTTP OPTIONS請求頭,用以判斷實際發送的請求是否安全
doTrace(HttpServletRequest request, HttpServletResponse response)
方法,用於處理 TRACE 類型的請求,方法以下:
protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 若是 dispatchTraceRequest 爲 true ,則處理該請求,默認爲 false if (this.dispatchTraceRequest) { // 處理請求 processRequest(request, response); // 若是響應的內容類型爲 "message/http" ,則不須要交給父方法處理 if ("message/http".equals(response.getContentType())) { // Proper TRACE response coming from a handler - we're done. return; } } // 調用父方法 super.doTrace(request, response); }
回顯服務器收到的請求,主要用於測試或診斷,筆者目前還沒接觸過😈
processRequest(HttpServletRequest request, HttpServletResponse response)
方法,用於處理請求,方法以下:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // <1> 記錄當前時間,用於計算處理請求花費的時間 long startTime = System.currentTimeMillis(); // <2> 記錄異常,用於保存處理請求過程當中發送的異常 Throwable failureCause = null; // <3> TODO LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext(); LocaleContext localeContext = buildLocaleContext(request); // <4> TODO RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // <5> TODO WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // <6> TODO initContextHolders(request, localeContext, requestAttributes); try { // <7> 執行真正的邏輯 doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; // <8> 記錄拋出的異常 throw ex; } catch (Throwable ex) { failureCause = ex; // <8> 記錄拋出的異常 throw new NestedServletException("Request processing failed", ex); } finally { // <9> TODO resetContextHolders(request, previousLocaleContext, previousAttributes); // <10> TODO if (requestAttributes != null) { requestAttributes.requestCompleted(); } // <11> 若是日誌級別爲 DEBUG,則打印請求日誌 logResult(request, response, failureCause, asyncManager); // <12> 發佈 ServletRequestHandledEvent 請求處理完成事件 publishRequestHandledEvent(request, response, startTime, failureCause); } }
<1>
記錄當前時間,用於計算處理請求花費的時間
<2>
記錄異常,用於保存處理請求過程當中發送的異常
<7>
【核心】調用 doService(HttpServletRequest request, HttpServletResponse response)
抽象方法,執行真正的邏輯,由 DispatcherServlet
實現,因此這就是 DispatcherServlet 處理請求的真正入口
<8>
記錄執行過程拋出的異常,最終在 finally
的代碼段中使用。
<11>
若是日誌級別爲 DEBUG,則打印請求日誌
<12>
調用 publishRequestHandledEvent
方法,經過 WebApplicationContext 發佈 ServletRequestHandledEvent 請求處理完成事件,目前好像 Spring MVC 沒有監聽這個事件,能夠本身寫一個監聽器用於獲取請求信息,示例以下:
@Component @Log4j2 public class ServletRequestHandledEventListener implements ApplicationListener<ServletRequestHandledEvent>{ @Override public void onApplicationEvent(ServletRequestHandledEvent event) { log.info("請求描述:{}", event.getDescription()); log.info("請求路徑:{}", event.getRequestUrl()); log.info("開始時間:{}", event.getTimestamp()); log.info("請求耗時:{}", event.getProcessingTimeMillis()); log.info("狀 態 碼:{}", event.getStatusCode()); log.info("失敗緣由:{}", event.getFailureCause()); } }
到這裏,FrameworkServlet 算是講完了,接下來就要開始講 DispatcherServlet 這個核心類了
org.springframework.web.servlet.DispatcherServlet
核心類,做爲 Spring MVC 的核心類,承擔調度器的角色,協調各個組件進行工做,處理請求,一塊兒來揭開這神祕的面紗吧😈
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; private static final Properties defaultStrategies; static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized by application developers. try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } }
會從 DispatcherServlet.properties
文件中加載默認的組件實現類,將相關配置加載到 defaultStrategies
中,文件以下:
### org.springframework.web.servlet.DispatcherServlet.properties # Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager # Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
能夠看到各個組件的默認實現類
/** MultipartResolver used by this servlet. multipart 數據(文件)處理器 */ @Nullable private MultipartResolver multipartResolver; /** LocaleResolver used by this servlet. 語言處理器,提供國際化的支持 */ @Nullable private LocaleResolver localeResolver; /** ThemeResolver used by this servlet. 主題處理器,設置須要應用的總體樣式 */ @Nullable private ThemeResolver themeResolver; /** List of HandlerMappings used by this servlet. 處理器匹配器,返回請求對應的處理器和攔截器們 */ @Nullable private List<HandlerMapping> handlerMappings; /** List of HandlerAdapters used by this servlet. 處理器適配器,用於執行處理器 */ @Nullable private List<HandlerAdapter> handlerAdapters; /** List of HandlerExceptionResolvers used by this servlet. 異常處理器,用於解析處理器發生的異常 */ @Nullable private List<HandlerExceptionResolver> handlerExceptionResolvers; /** RequestToViewNameTranslator used by this servlet. 視圖名稱轉換器 */ @Nullable private RequestToViewNameTranslator viewNameTranslator; /** FlashMapManager used by this servlet. FlashMap 管理器,負責重定向時保存參數到臨時存儲(默認 Session)中 */ @Nullable private FlashMapManager flashMapManager; /** List of ViewResolvers used by this servlet. 視圖解析器,根據視圖名稱和語言,獲取 View 視圖 */ @Nullable private List<ViewResolver> viewResolvers; public DispatcherServlet() { super(); setDispatchOptionsRequest(true); } public DispatcherServlet(WebApplicationContext webApplicationContext) { super(webApplicationContext); setDispatchOptionsRequest(true); }
定義了九個組件,在組件預覽中已經作過簡單介紹了
構造方法中都會設置 dispatchOptionsRequest
爲 true
,在父類 FrameworkServlet 中能夠看到,若是請求是 OPTIONS
則會處理請求
onRefresh(ApplicationContext context)
方法,初始化 Spring MVC 的各個組件,方法以下:
@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } /** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ protected void initStrategies(ApplicationContext context) { // 初始化 MultipartResolver initMultipartResolver(context); // 初始化 LocaleResolver initLocaleResolver(context); // 初始化 ThemeResolver initThemeResolver(context); // 初始化 HandlerMapping initHandlerMappings(context); // 初始化 HandlerAdapter initHandlerAdapters(context); // 初始化 HandlerExceptionResolver initHandlerExceptionResolvers(context); // 初始化 RequestToViewNameTranslator initRequestToViewNameTranslator(context); // 初始化 ViewResolver initViewResolvers(context); // 初始化 FlashMapManager initFlashMapManager(context); }
建立 Servlet WebApplicationContext 容器後會觸發該方法,在《WebApplicationContext 容器的初始化》的 FrameworkServlet小節的 onRefresh 方法中提到過
能夠看到每一個方法會初始化構造方法中的每一個組件
doService(HttpServletRequest request, HttpServletResponse response)
方法,DispatcherServlet 的處理請求的入口方法,代碼以下:
@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { // <1> 若是日誌級別爲 DEBUG,則打印請求日誌 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. // <2> 保存當前請求中相關屬性的一個快照 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. // <3> 設置 Spring 框架中的經常使用對象到 request 屬性中 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()); // <4> FlashMap 的相關配置 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 { // <5> 執行請求的分發 doDispatch(request, response); } finally { // <6> 異步處理相關 if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null) { restoreAttributesAfterInclude(request, attributesSnapshot); } } } }
logRequest(HttpServletRequest request)
方法,若是日誌級別爲 DEBUG,則打印請求日誌webApplicationContext
、localeResolver
、themeResolver
doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,執行請求的分發doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,行請求的分發,在開始看具體的代碼實現以前,咱們在來回味下這張圖片:
這張圖,更多的反應的是 DispatcherServlet 的 doDispatch(...)
方法的核心流程,方法以下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; // <1> 獲取異步管理器 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { // <2> 檢測請求是否爲上傳請求,若是是則經過 multipartResolver 將其封裝成 MultipartHttpServletRequest 對象 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // <3> 得到請求對應的 HandlerExecutionChain 對象(HandlerMethod 和 HandlerInterceptor 攔截器們) mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { // <3.1> 若是獲取不到,則根據配置拋出異常或返回 404 錯誤 noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // <4> 得到當前 handler 對應的 HandlerAdapter 對象 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. // <4.1> 處理有Last-Modified請求頭的場景 String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { // 不清楚爲何要判斷方法類型爲'HEAD' // 獲取請求中服務器端最後被修改時間 long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // <5> 前置處理 攔截器 // 注意:該方法若是有一個攔截器的前置處理返回false,則開始倒序觸發全部的攔截器的 已完成處理 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. // <6> 真正的調用 handler 方法,也就是執行對應的方法,並返回視圖 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // <7> 若是是異步 if (asyncManager.isConcurrentHandlingStarted()) { return; } // <8> 無視圖的狀況下設置默認視圖名稱 applyDefaultViewName(processedRequest, mv); // <9> 後置處理 攔截器 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; // <10> 記錄異常 } 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); } // <11> 處理正常和異常的請求調用結果 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { // <12> 已完成處理 攔截器 triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { // <12> 已完成處理 攔截器 triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { // <13.1> Asyn if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } // <13.1> 若是是上傳請求則清理資源 else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
得到 WebAsyncManager 異步處理器,暫時忽略
【文件】調用 checkMultipart(HttpServletRequest request)
方法,檢測請求是否爲上傳請求,若是是則經過 multipartResolver
組件將其封裝成 MultipartHttpServletRequest 對象,便於獲取參數信息以及文件
【處理器匹配器】調用 getHandler(HttpServletRequest request)
方法,經過 HandlerMapping 組件得到請求對應的 HandlerExecutionChain
處理器執行鏈,包含 HandlerMethod
處理器和 HandlerInterceptor
攔截器們
【處理器適配器】調用 getHandlerAdapter(Object handler)
方法,得到當前處理器對應的 HandlerAdapter 適配器對象
處理有 Last-Modified 請求頭的場景,暫時忽略
【攔截器】調用 HandlerExecutionChain
執行鏈的 applyPreHandle(HttpServletRequest request, HttpServletResponse response)
方法,攔截器的前置處理
若是出現攔截器前置處理失敗,則會調用攔截器的已完成處理方法(倒序)
【重點】調用 HandlerAdapter
適配器的 handle(HttpServletRequest request, HttpServletResponse response, Object handler)
方法,真正的執行處理器,也就是執行對應的方法(例如咱們定義的 Controller 中的方法),並返回視圖
若是是異步,則直接 return
,注意,仍是會執行 finally
中的代碼
調用 applyDefaultViewName(HttpServletRequest request, ModelAndView mv)
方法,ModelAndView 不爲空,可是沒有視圖,則設置默認視圖名稱,使用到了 viewNameTranslator
視圖名稱轉換器組件
【攔截器】調用 HandlerExecutionChain
執行鏈的 applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv)
方法,攔截器的後置處理(倒序)
記錄異常,注意,此處僅僅記錄,不會拋出異常,而是統一交給 <11>
處理
【處理執行結果】調用 processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
方法,處理正常和異常的請求調用結果,包含頁面渲染
【攔截器】若是上一步發生了異常,則調用 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex)
方法,即調用 HandlerInterceptor
執行鏈的 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,攔截器已完成處理(倒序)
finally
代碼塊,異步處理,暫時忽略,若是是涉及到文件的請求,則清理相關資源
上面將 DispatcherServlet 處理請求的整個流程步驟都列出來了,涉及到的組件分別在後續的文檔中將分開進行分析
checkMultipart(HttpServletRequest request)
方法,檢測請求是否爲上傳請求,若是是則經過 multipartResolver
組件將其封裝成 MultipartHttpServletRequest 對象
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException { // 若是該請求是一個涉及到 multipart (文件)的請求 if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) { if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) { if (request.getDispatcherType().equals(DispatcherType.REQUEST)) { logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter"); } } else if (hasMultipartException(request)) { logger.debug("Multipart resolution previously failed for current request - " + "skipping re-resolution for undisturbed error rendering"); } else { try { // 將 HttpServletRequest 請求封裝成 MultipartHttpServletRequest 對象,解析請求裏面的參數以及文件 return this.multipartResolver.resolveMultipart(request); } catch (MultipartException ex) { if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) { logger.debug("Multipart resolution failed for error dispatch", ex); // Keep processing error dispatch with regular request handle below } else { throw ex; } } } } // If not returned before: return original request. return request; }
getHandler(HttpServletRequest request)
方法,經過 HandlerMapping 組件得到請求對應的 HandlerExecutionChain
處理器執行鏈,包含 HandlerMethod
處理器和 HandlerInterceptor
攔截器們
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
getHandlerAdapter(Object handler)
方法,得到當前處理器對應的 HandlerAdapter 適配器對象
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
applyDefaultViewName(HttpServletRequest request, ModelAndView mv)
方法,ModelAndView 不爲空,可是沒有視圖,則設置默認視圖名稱
private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception { if (mv != null && !mv.hasView()) { String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { mv.setViewName(defaultViewName); } } } @Nullable protected String getDefaultViewName(HttpServletRequest request) throws Exception { // 使用到了 `viewNameTranslator` 視圖名稱轉換器組件 return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null); }
processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception)
方法,處理正常和異常的請求調用結果,方法以下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { // <1> 標記是否爲處理生成異常的 ModelAndView 對象 boolean errorView = false; // <2> 若是該請求出現異常 if (exception != null) { // 狀況一,從 ModelAndViewDefiningException 中得到 ModelAndView 對象 if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } // 狀況二,處理異常,生成 ModelAndView 對象 else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); // 標記 errorView errorView = (mv != null); } } // Did the handler return a view to render? // <3> 是否進行頁面渲染 if (mv != null && !mv.wasCleared()) { // <3.1> 渲染頁面 render(mv, request, response); // <3.2> 清理請求中的錯誤消息屬性 // 由於上述的狀況二中 processHandlerException 會經過 WebUtils 設置錯誤消息屬性,因此這裏得清理一下 if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } } // <4> 若是是異步 if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } // <5> 已完成處理 攔截器 if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); } }
標記是否爲處理生成異常的 ModelAndView 對象
若是該請求出現異常
processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,處理異常,生成 ModelAndView 對象若是 ModelAndView 不爲空且沒有被清理,例如你如今使用最多的 @ResponseBody 這裏就爲空,不須要渲染
render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法,渲染頁面2
步中狀況二生成的 ModelAndView 對象,則須要清理請求中的錯誤消息屬性,由於上述的狀況二會經過 WebUtils 設置錯誤消息屬性,因此這裏得清理一下若是是異步請求,則直接 return
正常狀況下,調用 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, Exception ex)
方法,即調用 HandlerInterceptor
執行鏈的 triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
方法,攔截器已完成處理(倒序)
processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,處理異常,生成 ModelAndView 對象
@Nullable protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 屬性 request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); // Check registered HandlerExceptionResolvers... // <a> 遍歷 HandlerExceptionResolver 數組,解析異常,生成 ModelAndView 對象 ModelAndView exMv = null; if (this.handlerExceptionResolvers != null) { // 遍歷 HandlerExceptionResolver 數組 for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) { // 解析異常,生成 ModelAndView 對象 exMv = resolver.resolveException(request, response, handler, ex); // 生成成功,結束循環 if (exMv != null) { break; } } } // <b> 狀況一,生成了 ModelAndView 對象,進行返回 if (exMv != null) { // ModelAndView 對象爲空,則返回 null if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // We might still need view name translation for a plain error model... // 沒有視圖則設置默認視圖 if (!exMv.hasView()) { String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { exMv.setViewName(defaultViewName); } } // 打印日誌 if (logger.isTraceEnabled()) { logger.trace("Using resolved error view: " + exMv, ex); } if (logger.isDebugEnabled()) { logger.debug("Using resolved error view: " + exMv); } // 設置請求中的錯誤消息屬性 WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } // <c> 狀況二,未生成 ModelAndView 對象,則拋出異常 throw ex; }
<a>
處,遍歷 HandlerExceptionResolver 數組,調用 HandlerExceptionResolver#resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
方法,解析異常,生成 ModelAndView 對象
<b>
處,狀況一,生成了 ModelAndView 對象,邏輯比較簡單
<c>
處,狀況二,未生成 ModelAndView 對象,則拋出異常
render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response)
方法,渲染 ModelAndView,方法以下:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. // <1> 解析 request 中得到 Locale 對象,並設置到 response 中 Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale); // 得到 View 對象 View view; String viewName = mv.getViewName(); // 狀況一,使用 viewName 得到 View 對象 if (viewName != null) { // We need to resolve the view name. // <2.1> 使用 viewName 得到 View 對象 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { // 獲取不到,拋出 ServletException 異常 throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + getServletName() + "'"); } } // 狀況二,直接使用 ModelAndView 對象的 View 對象 else { // No need to lookup: the ModelAndView object contains the actual View object. // <2.2> 直接使用 ModelAndView 對象的 View 對象 view = mv.getView(); if (view == null) { // 獲取不到,拋出 ServletException 異常 throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " + "View object in servlet with name '" + getServletName() + "'"); } } // Delegate to the View object for rendering. // 打印日誌 if (logger.isTraceEnabled()) { logger.trace("Rendering view [" + view + "] "); } try { // <3> 設置響應的狀態碼 if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // <4> 渲染頁面 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error rendering view [" + view + "]", ex); } throw ex; } }
調用 LocaleResolver
的 resolveLocale(HttpServletRequest request)
方法,從 request
中得到 Locale 對象,並設置到 response
中
得到 View 對象,有兩種狀況
調用 resolveViewName
方法,使用 viewName
經過得到 View 對象,方法以下:
@Nullable protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception { if (this.viewResolvers != null) { // 遍歷 ViewResolver 數組 for (ViewResolver viewResolver : this.viewResolvers) { // 根據 viewName + locale 參數,解析出 View 對象 View view = viewResolver.resolveViewName(viewName, locale); // 解析成功,直接返回 View 對象 if (view != null) { return view; } } } return null; }
直接使用 ModelAndView 對象的 View 對象
設置響應的狀態碼
調用 View
的 render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
方法,渲染視圖
本文對 Spring MVC 處理請求的整個過程進行了分析,核心就是經過 DispatcherServlet
協調各個組件工做,處理請求,由於 DispatcherServlet
是一個 Servlet,在 Servlet 容器中,會將請求交由它來處理。
經過本文對 DispatcherServlet
是如何處理請求已經有了一個總體的認識,不過在整個處理過程當中涉及到的各個 Spring MVC 組件尚未進行分析,對於許多細節存在疑惑,不要慌,那麼接下來會對每個 Spring MVC 組件進行分析。這樣,便於咱們對 Spring MVC 的理解,而後再回過頭來思考 DispatcherServlet
這個類,可以更好的將這些組件串聯在一塊兒。先總體,後局部,逐步逐步抽絲剝繭,看清理透。
流程示意圖,來自 SpringMVC - 運行流程圖及原理分析
![]()
代碼序列圖
![]()
流程示意圖,來自《看透 Spring MVC 源代碼分析與實踐》 書籍中的第 123 頁
![]()
參考文章:芋道源碼《精盡 Spring MVC 源碼分析》