SpringMVC 源碼分析之 DispatcherServlet

前面鬆哥和你們聊了 DispatcherServlet 的父類 FrameworkServlet,你們從中瞭解到在 DispatcherServlet 中,方法執行的入口應該是 doService。若是小夥伴們還沒看前面的分析,能夠先看下,這有助於理解本文,傳送門SpringMVC 源碼分析之 FrameworkServletjava

即便你沒看過 DispatcherServlet 的源碼,估計也據說過:DispatcherServlet 是 SpringMVC 的大腦,它負責整個 SpringMVC 的調度工做,是 SpringMVC 中最最核心的類,SpringMVC 整個頂層架構設計都體如今這裏,因此搞明白 DispatcherServlet 的源碼,基本上 SpringMVC 的工做原理也就瞭然於胸了。ios

通過上篇文章的分析,你們已經知道 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);
    }
}

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

  1. 首先判斷當前請求是否是 include 請求,若是是 include,則對 request 的 attribute 作一個快照備份,在最後的 finally 中再對備份的屬性進行還原。
  2. 接下來對 request 設置一些常見屬性,例如應用上下文、國際化的解析器、主題解析器等等,這些東西在初始化的時候已經準備好了,這裏只是應用(初始化過程參見SpringMVC 初始化流程分析一文)。
  3. 接下來處理 flashMap,若是存在 flashMap 則進行復原,這一塊鬆哥在以前的文章中和小夥伴們已經分享過了,傳送門SpringMVC 中的參數還能這麼傳遞?漲姿式了!
  4. 接下來處理 RequestPath,將請求路徑對象化以備後續使用(在後面的請求映射匹配時會用到)。
  5. 調用 doDispatch 方法進行下一步處理。
  6. 還原快照屬性、還原 RequestPath。

因此說這段代碼並不難理解,它的核心在於 doDispatch 方法,因此接下來咱們就來看看 doDispatch 方法。異步

doDispatch

doDispatch 方法所作的事情就比較多了,咱們來看下:async

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 方法其實主要作了兩方面的事情:請求處理以及頁面渲染,咱們先來看看初始變量的含義:post

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

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

  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 方法比較熟悉了,固然這裏還涉及到不少組件,這些組件鬆哥將在後面的文章中和你們逐一進行分析。

相關文章
相關標籤/搜索