spring源碼------一個請求在spring中的處理過程(從FrameworkServlet規(guī)范到DispatcherServlet)代碼及流程圖說明 (2)

?前面的文章已經(jīng)講了從一個請求在spring中的處理過程(從Servlet規(guī)范到FrameworkServlet)代碼及流程圖說明接下來就是壳影,從FrameworkServletDispatcherServlet的部分進行分析了腰池。

1.從FrameworkServletDispatcherServlet

?在FrameworkServletprocessRequest方法中,有調用doService方法的這個步驟撩轰。這個方法是一個抽象方法鸣戴,必須由子類來實現(xiàn)啃沪。這個方法是處理GET, POST, PUT , DELETE請求具體邏輯的方法。

1.1 從FrameworkServlet進入到DispatcherServletprocessRequest

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            ......
            //進行業(yè)務處理
            doService(request, response);
            ......
}

?所以我們可以直接到DispatcherServlet中進一步的分析窄锅。直接看對應的doService方法

1.2 進行真正請求處理前的準備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.
        //如果是一個include請求创千,<jsp:incluede page="xxx.jsp"/> 這種中,可能在一個請求(A)中嵌套了另外的一個請求(B)入偷,因此需要備份當前請求(A)
        Map<String, Object> attributesSnapshot = null;
        //是否是一個include請求追驴,通過request中的javax.servlet.include.request_uri屬性判斷,JSP在運行期間是會被編譯成相應的Servlet類來運行的疏之,
        // 所以在Servlet中也會有類似的功能和調用語法殿雪,這就是RequestDispatch.include()方法,在一個被別的servlet使用RequestDispatcher的include方法調用過的servlet中,
        // 如果它想知道那個調用它的servlet的上下文信息該怎么辦呢锋爪,那就可以通過request中的attribute中的如下屬性獲缺铩:javax.servlet.include.request_uri
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                //獲取包含的請求中的獲取A請求的內(nèi)部B請求設定的spring的策略
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
        // Make framework objects available to handlers and view objects.
        //設置web應用上下文到請求中,
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        //設置本地解析器
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        //設置主題解析器
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        //設置主題其骄,如果沒有設置則為null,默認的為WebApplicationContext
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
        //將request跟response保存到一個FlashMap中亏镰,F(xiàn)lashMap用來將一個請求跟另外一個請求關聯(lián)起來,通常在redirect的時候有用
        if (this.flashMapManager != null) {
            //如果當前請求的FlashMap在之前的請求中保存過了拯爽,則取出來索抓,并去除對應的緩存
            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
            //保存FlashMap到屬性中
            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 {
            //進行請求分發(fā)處理
            doDispatch(request, response);
        }
        finally {
            //在FrameworkServlet中會生成WebAsyncManager
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                // Restore the original attribute snapshot, in case of an include.
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

?這里對上面的流程總結一下:

  1. 檢查當前的請求中是否包含另外的一個請求(也就是請求包含),如果是這種情況需要把內(nèi)部請求的屬性作為快照保存起來。(個人對于這一塊還是有點沒有理解清楚的纸兔,歡迎指點)
  2. 設置對應的web請求上下惰瓜,本地解析器,主題解析器汉矿,主題到request中
  3. 檢查flashMapManager是不是null(這個不會為空崎坊,在initFlashMapManager方法初始化的時候回創(chuàng)建),如果不是空則吧對應的request, respons設置進一個FlashMap對象中洲拇,同時設置對應的輸入奈揍,輸出跟flashMap管理對象到request
  4. 進行請求分發(fā)處理doDispatch方法
  5. 最后處理異步請求處理相關的邏輯,主要檢查這個請求的異步處理邏輯是不是正在處理赋续,是的就將上面保存的內(nèi)部請求設置到request中男翰。

?這里涉及到請求包含(include)跟請求轉發(fā)(forward)。關于這兩個東西可以百度一下或者參考下面這個文章請求包含(Include)和請求轉發(fā)(Forward)這個位置理解有點費力纽乱。個人理解可能也有點缺陷蛾绎,歡迎指正。

?在準備好了處理請求需要的相關環(huán)境跟參數(shù)的之后鸦列,就是進行請求的分發(fā)處理了租冠,之所以叫分發(fā)是因為每個請求都有對應的處理類,現(xiàn)在進入到分發(fā)的邏輯方法doDispatch

1.3 進行請求分發(fā)的doDispatch

?因為doDispatch這個方法的內(nèi)部邏輯比較復雜(方法內(nèi)部調用了其他的復雜的方法邏輯)薯嗤,一篇博客肯定寫不完顽爹,所以這里主要寫整個方法內(nèi)的邏輯。

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        //從request中獲取WebAsyncManager
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                //檢查是不是multipart請求并將請求轉化為MultipartHttpServletRequest類型的
                processedRequest = checkMultipart(request);
                //如果請求不是原來的request請求骆姐,則表示是multipart請求并且解析過的
                multipartRequestParsed = (processedRequest != request);

                //獲取封裝了HandlerInterceptor的HandlerExecutionChain
                mappedHandler = getHandler(processedRequest);
                //如果不存在對應的處理鏈則返回
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                //從HandlerExecutionChain中獲取Handler然后尋找合適的HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                //獲取請求方式
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                //檢查是不是GET請求
                if (isGet || "HEAD".equals(method)) {
                    //檢查當前的get類型的請求的最后修改時間是不是存在的镜粤,這個參數(shù)用來減少數(shù)據(jù)傳輸用
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    //如果瀏覽器請求中的最后請求時間跟服務器的最后修改時間一致,并且是get類型請求則直接返回玻褪。關于lastModified這個會說明
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                //調用mappedHandler中攔截器的applyPreHandle方法肉渴,如果對應的請求不符合規(guī)則則直接返回
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                //執(zhí)行對應的HandlerAdapter的Handler方法,拿到對應的視圖
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                //檢查當前的請求是否正在異步處理归园,如果是的則直接放棄并返回
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                //如果處理的結果返回的視圖是空的則使用默認的視圖黄虱,不為空則用處理的結果
                applyDefaultViewName(processedRequest, mv);
                //調用applyPostHandle方法對視圖進行返回后的處理
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                //進行錯誤視圖的處理
                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
                //如果mappedHandler不是null稚矿,則調用對應的mappedHandler中的AsyncHandlerInterceptor
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                //對流類型的請求庸诱,做后置的處理
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

?對于doDispatch方法的處理流程就跟網(wǎng)上的mvc處理流程圖一樣的,把整個流程包含在了一個方法里面晤揣。這里就用網(wǎng)上的一張圖具體說明

流程圖

  1. 首先會檢查當前的請求是不是multipart/form-data類型的請求桥爽,是的則將請求進行轉換為MultipartHttpServletRequest類型的request。(MultipartHttpServletRequestHttpServletRequest的子類)
  2. 獲取合適的獲取封裝了HandlerInterceptor集合的HandlerExecutionChain昧识,如果沒有找到合適的钠四,就直接返回404的錯誤頁面
  3. HandlerExecutionChain中獲取Handler然后尋找合適的HandlerAdapter
  4. 檢查是不是get請求,是的然后在檢查lastModified這個屬性選擇是直接返回還是進行后續(xù)的請求處理。(lastModified這個屬性后面說一下缀去,其實前面文章已經(jīng)說過了)
  5. 調用前面選擇的HandlerAdapterapplyPreHandle方法檢查請求是否符合定義的要求侣灶,不符合就返回。
  6. 調用前面選擇的HandlerAdapterhandle方法缕碎,進行邏輯的處理褥影,然后返回ModelAndView對象
  7. 檢查當前的請求是否正在異步處理,如果是的則直接放棄并返回
  8. 檢查返回的ModelAndView的視圖是不是null咏雌,是的則返回默認的凡怎,不是的則不處理
  9. 調用前面選擇的HandlerAdapterapplyPostHandle方法對視圖進行最后的處理
  10. 對正常返回的或者發(fā)生異常時生成的視圖進行處理,
  11. 對異步請求或multipart/form-data類型請求進行后續(xù)的處理

?對于lastModified這個屬性可以參考這篇文章HttpServlet---getLastModified與緩存

后續(xù)的

?雖然整個的request請求的邏輯只有這么多赊抖,但是里面的每個步驟都是比較復雜的统倒。這一篇講不完,因此挑選里面比較重要的幾個步驟進行分篇講解氛雪。主要以下幾個步驟

  1. HandlerExecutionChain的選擇
  2. HandlerAdapter的獲取跟handle方法邏輯
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末房匆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子报亩,更是在濱河造成了極大的恐慌坛缕,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捆昏,死亡現(xiàn)場離奇詭異赚楚,居然都是意外死亡,警方通過查閱死者的電腦和手機骗卜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門宠页,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寇仓,你說我怎么就攤上這事举户。” “怎么了遍烦?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵俭嘁,是天一觀的道長。 經(jīng)常有香客問我服猪,道長供填,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任罢猪,我火速辦了婚禮近她,結果婚禮上,老公的妹妹穿的比我還像新娘膳帕。我一直安慰自己粘捎,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著攒磨,像睡著了一般泳桦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上娩缰,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天蓬痒,我揣著相機與錄音,去河邊找鬼漆羔。 笑死梧奢,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的演痒。 我是一名探鬼主播亲轨,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸟顺!你這毒婦竟也來了惦蚊?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤讯嫂,失蹤者是張志新(化名)和其女友劉穎蹦锋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欧芽,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡莉掂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了千扔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憎妙。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖曲楚,靈堂內(nèi)的尸體忽然破棺而出厘唾,到底是詐尸還是另有隱情,我是刑警寧澤龙誊,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布抚垃,位于F島的核電站,受9級特大地震影響趟大,放射性物質發(fā)生泄漏鹤树。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一护昧、第九天 我趴在偏房一處隱蔽的房頂上張望魂迄。 院中可真熱鬧粗截,春花似錦惋耙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湿酸。三九已至,卻和暖如春灭美,著一層夾襖步出監(jiān)牢的瞬間推溃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工届腐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铁坎,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓犁苏,卻偏偏與公主長得像硬萍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子围详,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359