SpringMVC 源碼分析之 DispatcherServlet

松哥原創的 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);
 }
}

這裏的代碼並不長,我們來稍微分析一下:

  1. 首先判斷當前請求是不是 include 請求,如果是 include,則對 request 的 attribute 做一個快照備份,在最後的 finally 中再對備份的屬性進行還原。
  2. 接下來對 request 設置一些常見屬性,例如應用上下文、國際化的解析器、主題解析器等等,這些東西在初始化的時候已經準備好了,這裏只是應用(初始化過程參見 SpringMVC 初始化流程分析一文)。
  3. 接下來處理 flashMap,如果存在 flashMap 則進行復原,這一塊松哥在之前的文章中和小夥伴們已經分享過了,傳送門 SpringMVC 中的參數還能這麼傳遞?漲姿勢了!
  4. 接下來處理 RequestPath,將請求路徑對象化以備後續使用(在後面的請求映射匹配時會用到)。
  5. 調用 doDispatch 方法進行下一步處理。
  6. 還原快照屬性、還原 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 方法其實主要做了兩方面的事情:請求處理以及頁面渲染,我們先來看看初始變量的含義:

  1. processedRequest:這個用來保存實際上所用的 request 對象,在後面的流程中會對當前 request 對象進行檢查,如果是文件上傳請求,則會對請求重新進行封裝,如果不是文件上傳請求,則繼續使用原來的請求。
  2. mappedHandler:這是具體處理請求的處理器鏈,處理器鏈包含兩方面的東西:請求處理器和對應的 Interceptor。
  3. multipartRequestParsed:表示是否是文件上傳請求的標記。
  4. asyncManager:這是一個異步請求管理器。
  5. mv:這是最終渲染返回的 ModelAndView 對象。
  6. dispatchException:表示請求處理過程中所拋出的異常,這個異常不包括渲染過程拋出的異常。

接下來再來看看具體的處理邏輯:

  1. 首先通過 checkMultipart 檢查是不是文件上傳請求,如果是,則對當前 request 重新進行包裝,如果不是,則直接將參數返回。
  2. 如果 processedRequest 不等於 request,則說明當前請求是文件上傳請求(request 在 checkMultipart 方法中被重新封裝了),否則說明當前請求不是文件上傳請求。
  3. 根據當前請求,調用 getHandler 方法獲取請求處理器,如果沒找到對應的請求處理器,則調用 noHandlerFound 方法拋出異常或者給出 404。
  4. 接下來再調用 getHandlerAdapter 方法,根據當前的處理器找到處理器適配器。
  5. 然後處理 GET 和 HEAD 請求頭的 Last_Modified 字段。當瀏覽器第一次發起 GET 或者 HEAD 請求時,請求的響應頭中包含一個 Last-Modified 字段,這個字段表示該資源最後一次修改時間,以後瀏覽器再次發送 GET、HEAD 請求時,都會攜帶上該字段,服務端收到該字段之後,和資源的最後一次修改時間進行對比,如果資源還沒有過期,則直接返回 304 告訴瀏覽器之前的資源還是可以繼續用的,如果資源已經過期,則服務端會返回新的資源以及新的 Last-Modified。
  6. 接下來調用攔截器的 preHandle 方法,如果該方法返回 false,則直接 return 掉當前請求(攔截器的用法大家可以參考松哥之前錄的免費的 SpringMVC 視頻教程,裏邊有講,傳送門 硬核!松哥又整了一套免費視頻,搞起!)。
  7. 接下來執行 ha.handle 去調用真正的請求,獲取到返回結果 mv。
  8. 接下來判斷當前請求是否需要異步處理,如果需要,則直接 return 掉。
  9. 如果不需要異步處理,則執行 applyDefaultViewName 方法,檢查當前 mv 是否沒有視圖,如果沒有(例如方法返回值爲 void),則給一個默認的視圖名。
  10. 接下來調用 applyPostHandle 方法執行攔截器裏邊的 postHandle 方法。
  11. 接下來調用 processDispatchResult 方法對執行結果進行處理,包括異常處理、渲染頁面以及執行攔截器的 afterCompletion 方法都在這裏完成。
  12. 最後在 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源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章