該系列文件是本人在學習 Spring MVC 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋 Spring MVC 原始碼分析 GitHub 地址 進行閱讀
Spring 版本:5.2.4.RELEASE
該系列其他文件請檢視:《精盡 Spring MVC 原始碼分析 - 文章導讀》
在上一篇《WebApplicationContext 容器的初始化》文件中分析了 Spring MVC 是如何建立兩個容器的,其中建立Root WebApplicationContext 後,呼叫其refresh()
方法會觸發重新整理事件,完成 Spring IOC 初始化相關工作,會初始化各種 Spring Bean 到當前容器中,該系列文件暫不分析
我們先來了解一個請求是如何被 Spring MVC 處理的,由於整個流程涉及到的程式碼非常多,所以本文的重點在於解析整體的流程,主要講解 DispatcherServlet 這個核心類,弄懂了這個流程後,才能更好的理解具體的原始碼,回過頭再來看則會更加的豁然開朗
整體流程圖
Spring MVC 處理請求的流程大致如上圖所示
- 使用者的瀏覽器傳送一個請求,這個請求經過網際網路到達了我們的伺服器。Servlet 容器首先接待了這個請求,並將該請求委託給
DispatcherServlet
進行處理。 DispatcherServlet
將該請求傳給了處理器對映元件HandlerMapping
,並獲取到適合該請求的 HandlerExecutionChain 攔截器和處理器物件。- 在獲取到處理器後,
DispatcherServlet
還不能直接呼叫處理器的邏輯,需要進行對處理器進行適配。處理器適配成功後,DispatcherServlet
通過處理器介面卡HandlerAdapter
呼叫處理器的邏輯,並獲取返回值ModelAndView
物件。 - 之後,
DispatcherServlet
需要根據 ModelAndView 解析檢視。解析檢視的工作由ViewResolver
完成,若能解析成功,ViewResolver
會返回相應的 View 檢視物件。 - 在獲取到具體的 View 物件後,最後一步要做的事情就是由 View 渲染檢視,並將渲染結果返回給使用者。
以上就是 Spring MVC 處理請求的全過程,上面的流程進行了一定的簡化,主要涉及到最核心的元件,還有許多其他元件沒有表現出來,不過這並不影響大家對主過程的理解。
元件預覽
在上一篇《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 一下,嘻嘻~? 筆者沒接觸過
FrameworkServlet
雖然在上面的整體流程圖中,我們看到請求是直接被 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
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
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
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
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 這個核心類了
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
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 方法中提到過
可以看到每個方法會初始化構造方法中的每個元件
1. doService
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,則列印請求日誌 - 儲存當前請求中相關屬性的一個快照,作為非同步處理的屬性值,防止被修改,暫時忽略
- 設定 Spring 框架中的常用物件到 request 屬性中,例如
webApplicationContext
、localeResolver
、themeResolver
- FlashMap 的相關配置,暫時忽略
- 【重點】呼叫
doDispatch(HttpServletRequest request, HttpServletResponse response)
方法,執行請求的分發 - 非同步處理相關,暫時忽略
2. doDispatch【核心】
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
攔截器們- 如果獲取不到對應的執行鏈,則根據配置丟擲異常或返回 404 錯誤
-
【處理器介面卡】呼叫
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 處理請求的整個流程步驟都列出來了,涉及到的元件分別在後續的文件中將分開進行分析
2.1 checkMultipart
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;
}
2.2 getHandler
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;
}
2.3 getHandlerAdapter
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");
}
2.4 applyDefaultViewName
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);
}
2.5 processDispatchResult
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 物件
-
如果該請求出現異常
- 情況一,從 ModelAndViewDefiningException 中獲得 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)
方法,攔截器已完成處理(倒序)
2.5.1 processHandlerException
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 物件,則丟擲異常
2.5.2 render
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 原始碼分析》