初探DispatcherServlet#doDispatch
寫在前面
SpringBoot其實就是SpringMVC的簡化版本,對於request的處理流程大致是一樣的, 都要經過DispatcherServlet攔截之後通過相應的Handler去尋找對應的Controller處理業務最後返回ModelAndView做檢視解析之後渲染到前端頁面。
0x01 doDispatch
首先所有請求都會經過org/springframework/web/servlet/DispatcherServlet.java,這一點也可以根據該方法註釋瞭解到。
我們的請求會先進入到DispatcherServlet#doService方法中,在doService中呼叫了doDispatch,而doDispatch是實現大部分處理request邏輯的地方,大致可分為請求處理(如尋找相應controller,獲取ModelAndView,resolveView檢視解析等)和頁面渲染,下面是該方法程式碼。
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//將request物件重新儲存到processedRequest
HttpServletRequest processedRequest = request;
//處理器鏈
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
//獲取非同步請求管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//最終返回的ModelAndView物件
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);
}
}
}
}
0x02 前期處理
前面部分程式碼是對請求的一些前期處理,從processedRequest = checkMultipart(request);
開始進入對request的處理邏輯部分。
首先check該request是否為檔案上傳請求,如果是則重新封裝request,不是則把傳入的request原封不動return回來
之後判斷我們的requst是否在checkMultipart方法中封裝過(即request是檔案上傳請求),判斷的布林值結果賦值給multipartRequestParsed,此值類似於flag用作後面判斷,當是檔案上傳請求時在最後會清除檔案上傳過程中的臨時檔案。
0x02 getHandler
之後進入Handler部分,呼叫org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandler並返回executionChain賦值給mappedHandler,如果沒找到對應的handler和攔截器就會進入if中呼叫noHandlerFound丟擲異常。
org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandlerExecutionChain實現:
簡而概之,這裡返回值executionChain中封裝了2個重要的東西,之後會在doDispatch中被用到:
- 處理當前請求的Controller及其方法的資訊
- 當前請求所需要的攔截器資訊
0x03 getHandlerAdapter
下面呼叫getHandlerAdapter根據之前返回的executionChain拿到handler,再根據handler獲取適配的handlerAdapter處理器介面卡
這裡預設為RequestMappingHandlerAdapter優先順序最高,最終返回的也是它。
0x04 Last_Modified處理
之後處理GET和HEAD請求頭的 Last_Modified 欄位。
當瀏覽器第一次發起 GET 或者 HEAD 請求時,請求的響應頭中包含一個 Last-Modified 欄位,這個欄位表示該資源最後一次修改時間,以後瀏覽器再次傳送 GET、HEAD 請求時,都會攜帶上該欄位,服務端收到該欄位之後,和資源的最後一次修改時間進行對比,如果資源還沒有過期,則直接返回 304 告訴瀏覽器之前的資源還是可以繼續用的,如果資源已經過期,則服務端會返回新的資源以及新的 Last-Modified
0x04 applyPreHandler
接下來做了一個判斷,呼叫applyPreHandler()方法對所有的攔截器進行遍歷,如果發現攔截器的preHandle()方法返回false的話,則直接執行triggerAfterCompletion()方法,並返回false,執行停止,如果獲取的布林型別為true,則將對interceptorIndex進行賦值為1
0x05 handle
之後是handlerAdaptor調handle,去進行對handler的一個處理
這裡的chain比較複雜
org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter#handle
org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter#handleInternal
org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter#invokeHandlerMethod
org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod#invokeAndHandle
org/springframework/web/method/support/InvocableHandlerMethod#invokeForRequest
跟進到org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java#invokeAndHandle方法,這裡呼叫invokeFoRequest會返回returnValue,該方法會根據輸入的uri,呼叫相關的controller的方法獲取返回值,並將其返回給returnValue
,作為待查詢的模板檔名,再去傳給檢視解析器處理。(這裡因為我用的Controller方法中沒有返回值,所以returnValue為null)
最終層層返回值賦值給mv
0x06 非同步請求處理
下一步判斷是否需要進行非同步處理請求,需要的話return掉
0x07 applyDefaultViewName
接下來applyDefaultViewName方法判斷當前檢視是否為空,如果為空,呼叫getDefaultViewName方法獲取ModelAndView
但是因為這裡mav值為空,所以viewTemplateName會從uri中獲取,我們看下是如何處理defaultViewName
的,除錯之後發現最終在getViewName
方法中呼叫transformPath
對URL中的path
進行了處理
重點在於第3個if中stripFilenameExtension
方法
/org/springframework/util/StringUtils#stripFilenameExtension該方法會對字尾做一個清除(去掉.
及其之後的內容)並將該uri返回
最終通過mv.setViewName(defaultViewName);
將該uri賦值給mv
0x08 applyPostHandle
接下來呼叫 applyPostHandle 方法執行攔截器裡邊的 postHandle 方法。
0x09 processDispatchResult
之後會進入到processDispatchResult
方法,包括異常處理、渲染頁面以及執行攔截器的 afterCompletion 方法都在這裡完成。該方法中第1個if會被跳過,跟進第2個if中的render方法
在render
方法中,首先會獲取mv物件的viewName
,然後呼叫resolveViewName
方法,resolveViewName
方法最終會獲取最匹配的檢視解析器。
跟一下resolveViewName
方法,這裡涉及到兩個方法:1、首先通過getCandidateViews
篩選出resolveViewName
方法返回值不為null的檢視解析器新增到candidateViews
中; 2、之後通過getBestView
拿到最適配的解析器,getBestView中的邏輯是優先返回在candidateViews
存在重定向動作的view
,如果都不存在則根據請求頭中的Accept
欄位的值與candidateViews
的相關順序,並判斷是否相容來返回最適配的View
getCandidateViews:
getBestView:
這裡最終返回的是ThymeleafView
(不同情況會返回不同的檢視解析器,添加了Thymeleaf依賴會有ThymeleafView,也可能會有自定義的檢視解析器,返回值不唯一)之後ThymeleafView
呼叫了render
方法,繼續跟進
呼叫renderFragment
該方法在後面首先判斷viewTemplateName
是否包含::
,若包含則獲取解析器,呼叫parseExpression
方法將viewTemplateName
(也就是Controller中最後return的值)構造成片段表示式(~{}
)並解析執行。後面就不跟了,如果是Thymeleaf還會對錶達式進行預處理操作,不同的檢視解析器執行流程應該也是不一樣的。
0x10 cleanupMultipart
最後在 finally 程式碼塊中判斷是否開啟了非同步處理,如果開啟了,則呼叫相應的攔截器;如果請求是檔案上傳請求,則再呼叫 cleanupMultipart 方法清除檔案上傳過程產生的一些臨時檔案。
結語
簡單除錯跟了下DispatcherServlet。