松哥原創的 Spring Boot 視頻教程已經殺青,感興趣的小夥伴戳這裏-->Spring Boot+Vue+微人事視頻教程
前面松哥和大家聊了 DispatcherServlet 的父類 FrameworkServlet,大家從中瞭解到在 DispatcherServlet 中,方法執行的入口應該是 doService。如果小夥伴們還沒看前面的分析,可以先看下,這有助於理解本文,傳送門SpringMVC 源碼分析之 FrameworkServlet。
即使你沒看過 DispatcherServlet 的源碼,估計也聽說過:DispatcherServlet 是 SpringMVC 的大腦,它負責整個 SpringMVC 的調度工作,是 SpringMVC 中最最核心的類,SpringMVC 整個頂層架構設計都體現在這裏,所以搞明白 DispatcherServlet 的源碼,基本上 SpringMVC 的工作原理也就瞭然於胸了。
經過上篇文章的分析,大家已經知道 DispatcherServlet 的入口方法是 doService,所以今天我們就從 doService 方法開始看起,松哥將帶領大家,一步一步揭開 DispatcherServlet 的面紗。
doService
先來看 doService,把源碼先貼上來,然後我們逐步分析:
protected 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);
}
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()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
這裏的代碼並不長,我們來稍微分析一下:
-
首先判斷當前請求是不是 include 請求,如果是 include,則對 request 的 attribute 做一個快照備份,在最後的 finally 中再對備份的屬性進行還原。 -
接下來對 request 設置一些常見屬性,例如應用上下文、國際化的解析器、主題解析器等等,這些東西在初始化的時候已經準備好了,這裏只是應用(初始化過程參見 SpringMVC 初始化流程分析一文)。 -
接下來處理 flashMap,如果存在 flashMap 則進行復原,這一塊松哥在之前的文章中和小夥伴們已經分享過了,傳送門 SpringMVC 中的參數還能這麼傳遞?漲姿勢了!。 -
接下來處理 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 afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
這個方法比較長,涉及到很多組件的處理,這裏松哥先和大家把思路梳理暢通,各個組件的詳細用法松哥將在以後的文章中和大家仔細分享。
doDispatch 方法其實主要做了兩方面的事情:請求處理以及頁面渲染,我們先來看看初始變量的含義:
-
processedRequest:這個用來保存實際上所用的 request 對象,在後面的流程中會對當前 request 對象進行檢查,如果是文件上傳請求,則會對請求重新進行封裝,如果不是文件上傳請求,則繼續使用原來的請求。 -
mappedHandler:這是具體處理請求的處理器鏈,處理器鏈包含兩方面的東西:請求處理器和對應的 Interceptor。 -
multipartRequestParsed:表示是否是文件上傳請求的標記。 -
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 掉當前請求(攔截器的用法大家可以參考松哥之前錄的免費的 SpringMVC 視頻教程,裏邊有講,傳送門 硬核!松哥又整了一套免費視頻,搞起!)。 -
接下來執行 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) {
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 (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
可以看到,在 processDispatchResult 方法中首先對異常進行了處理,配置好異常對應的 ModelAndView,然後調用 render 方法對頁面進行渲染,最後通過 triggerAfterCompletion 方法去觸發攔截器的 afterCompletion 方法。
小結
至此,我們就把一個請求的大致流程和大家梳理完了,松哥畫了一張流程圖我們一起來看下:
這下相信大家對 doDispatch 方法比較熟悉了,當然這裏還涉及到很多組件,這些組件松哥將在後面的文章中和大家逐一進行分析。
本文分享自微信公衆號 - 江南一點雨(a_javaboy)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。