Spring MVC 運(yùn)行流程解析(含源碼分析)

章節(jié)目錄

  • Spring MVC DispatcherServlet 與 HttpServlet 關(guān)系類圖
  • Spring MVC 源碼分析Request 請(qǐng)求映射驱负、執(zhí)行、視圖解析流程
  • 總結(jié)-Spring MVC 運(yùn)行流程圖

1.Spring MVC DispatcherServlet 與 HttpServlet 關(guān)系類圖

1.1 什么是DispatcherServlet
源碼注釋如下所示:

Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.

譯文如下:

DispatcherServlet 是HTTP請(qǐng)求處理程序/控制器的中央調(diào)度程序(將請(qǐng)求映射到具體處理器(handler)上 )娱局,例如用于Web UI控制器或基于HTTP的遠(yuǎn)程服務(wù)導(dǎo)出器(webService)耻涛,調(diào)度器會(huì)將請(qǐng)求路由至已經(jīng)注冊(cè)好的具體的hadler,使得handler可以處理執(zhí)行相關(guān)的web請(qǐng)求奏赘,提供了請(qǐng)求與處理器之間的映射關(guān)系功能梁只,其實(shí)就是路由映射功能监嗜。

1.2 什么是HttpServlet

HttpServlet 是處理相關(guān)基于Http請(qǐng)求的處理程序音五,請(qǐng)求的相關(guān)信息被封裝成 HttpServletRequest對(duì)象苍蔬,其中Service() 方法通過獲取 HttpServletRequest 中的方法名 如 GET惭蟋、 POSTPUT等 request-method信息的獲取我碟,去invoke具體的doGet()放案、doPost()doPut()方法矫俺,最終將執(zhí)行完業(yè)務(wù)邏輯獲取到的處理數(shù)據(jù)通過HttpServletResponse對(duì)象返回給客戶端吱殉。所以最終request請(qǐng)求結(jié)果還是從HttpServlet中的service()返回的

那么這兩者之間有什么關(guān)系呢?
如下圖所示DispatcherServlet與HttpServlet之間的類圖關(guān)系:


其中最重要的是FrameworkServlet厘托。
源碼注釋如下:

Base servlet for Spring's web framework. Provides integration with
a Spring application context, in a JavaBean-based overall solution.

譯文如下:

Spring web 框架中的基礎(chǔ)Servlet友雳,將Spring 相關(guān)的ApplicationContext 集成進(jìn)來。方便我們?cè)诤笃谑褂肧pring IOC 容器中注冊(cè)的各種屬性的類對(duì)象铅匹。

FrameworkServlet 整合Spring WebApplicationContext 對(duì)象源碼如下:

    /**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
    ...
 return wac;
}

其中獲取web應(yīng)用程序上下文的代碼段為:

WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());

FrameworkServlet 對(duì) extends 自 HttpServlet 的service()方法進(jìn)行了override()


override service()

super.service()即調(diào)用HttpServlet中的Service()方法
可以看到Service()方法根據(jù)request.method 去調(diào)用具體的doxxx()方法押赊,這里FrameworkServlet 對(duì) doxxx()方法也進(jìn)行了override()。

如下為FrameworkServlet 中doGet()方法源碼

override doGet()

其中的processRequest()方法源碼如下所示:

    /**
     * Process this request, publishing an event regardless of the outcome.
     * <p>The actual event handling is performed by the abstract
     * {@link #doService} template method.
     */
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }

            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
  • 其中最重要的是doService()方法伊群,這個(gè)doService()方法被聲明為抽象方法考杉,在DispatcherServlet 做具體實(shí)現(xiàn)。
    源碼實(shí)現(xiàn)如下:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(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<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
                //為請(qǐng)求設(shè)置具體的屬性舰始。
        // 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());

        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);

        try {
                        //調(diào)用doDispatch()崇棠,將請(qǐng)求分配給具體的handler去處理。實(shí)際上第二節(jié)會(huì)具體分析doDispatch()方法
            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);
                }
            }
        }
    }

1.3 Spring Web 對(duì)Request的執(zhí)行流程
所以請(qǐng)求的整個(gè)執(zhí)行流程依據(jù)之前的HttpServlet知識(shí)積累(未debug)可以大致總結(jié)如下(第二節(jié)會(huì)debug源碼丸卷,用來驗(yàn)證我們總結(jié)的這個(gè)流程)
即:

1.請(qǐng)求到達(dá)dispatcherServlet枕稀,(非初次請(qǐng)求,初次請(qǐng)求會(huì)涉及dispatcherServlet初始化,調(diào)用init()方法)谜嫉。
2.dispatcherServlet 執(zhí)行service()方法萎坷,因Dispatcher類繼承FrameworkServlet,所以調(diào)用父類的service()方法沐兰。
3.service()調(diào)用FrameworkServlet 中具體的doxxx()方法
4.FrameworkServlet 具體的 doxxx()方法調(diào)用processRequest()方法哆档。
5.processRequest()方法調(diào)用dispatcherServlet 的 doService()方法。
6.dispatcherServlet 的 doService()方法調(diào)用doDispatch()方法住闯。

注意:上述根方法-service()方法被servlet容器 Servlet container顯示調(diào)用瓜浸。

2.Spring MVC 源碼分析Request 請(qǐng)求映射、執(zhí)行比原、視圖解析流程

簡(jiǎn)單的helloword級(jí)別的web項(xiàng)目,搭建方式可以略過插佛。主要是debug開始的地方我們需要確定,因?yàn)橛蠬ttpServlet源碼分析的積累量窘,那么我們直接在DispatcherServlet中的Service方法中打斷點(diǎn)就可以了雇寇,因?yàn)镈ispatcherServlet繼承了FrameworkServlet,F(xiàn)rameworkServlet對(duì)HttpServlet中的service()方法進(jìn)行了override,所以程序入口斷點(diǎn)應(yīng)該打在FrameworkServlet 中的service() 方法锨侯,接下來就是實(shí)操演示:

注意:本源碼分析的是Spring 4.1版本

2.0 debug的目的

了解 request 到具體 handler 的執(zhí)行流程嫩海。

2.1 FrameworkServlet 中 service() 打斷點(diǎn)

2

2.2 開啟debug模式

image.png

2.3 開始debug

image.png

執(zhí)行父類service()方法


image.png

執(zhí)行doGet()方法


image.png

執(zhí)行processRequest()方法


執(zhí)行doService()方法


執(zhí)行doDispatch方法


獲取請(qǐng)求對(duì)應(yīng)的handler


繼續(xù)debug hm.getHandler(request)看看這其中發(fā)生了什么?
通過SimpleUrlHandlerMapping, 發(fā)現(xiàn)并不能獲取到 對(duì)應(yīng)的 handler(HandlerExcutionChain對(duì)象)识腿,
繼續(xù)foreach


通過EndpointHandlerMapping, 發(fā)現(xiàn)并不能獲取到 對(duì)應(yīng)的 handler(HandlerExcutionChain對(duì)象)出革,
繼續(xù)foreach
image.png

image.png

最終我們通過RequestMappingHandlerMapping對(duì)象獲取到了對(duì)應(yīng)的handler對(duì)象。
可以看下handler對(duì)象是什么東東渡讼?


image.png

所以handlerExcutionChain 對(duì)象 包含有handler對(duì)象骂束、interceptor對(duì)象。

到此我們通過requestMappingHandlerMapping 獲取到了請(qǐng)求對(duì)應(yīng)的handler成箫。

image.png

接下來需要以handler為參數(shù)獲取真正處理請(qǐng)求的handlerAdaptor


image.png

接下來執(zhí)行 handlerAdaptor 中 handler()方法

image.png

返回mv展箱,需要注意的是,返回mv 其實(shí)是對(duì)Controller 中業(yè)務(wù)方法的調(diào)用其實(shí)使用到了反射蹬昌。

image.png
image.png
image.png

注意在返回mv之前 通過handlerExcutionChain對(duì)象可以調(diào)用applyPreHandler 方法混驰,可以在返回mv之前做預(yù)先處理工作。

返回mv之后皂贩,可以通過handlerExcutionChain對(duì)象可以調(diào)用applyPreHandler 方法對(duì)返回的mv做修改栖榨。我們只需要實(shí)現(xiàn) handlerInterceptor類并實(shí)現(xiàn)配置就可以了。

最后一步執(zhí)行視圖渲染的工作明刷,這一步是在dispatcherServlet中完成的婴栽。


最終請(qǐng)求結(jié)果



注意:由于返回結(jié)果為String 類型的value,不涉及視圖解析,所以render 方法并沒有執(zhí)行辈末。

3.總結(jié)-Spring MVC 運(yùn)行流程圖

image.png
image.png

對(duì)上述流程圖的解釋:

  • 用戶發(fā)起請(qǐng)求到前端控制器(Controller)
  • 前端控制器沒有處理業(yè)務(wù)邏輯的能力愚争,需要找到具體的模型對(duì)象處理(Handler),到處理器映射器(HandlerMapping)中查找Handler對(duì)象(Model)挤聘。
  • HandlerMapping返回執(zhí)行鏈轰枝,包含了2部分內(nèi)容: ① Handler對(duì)象、② 攔截器數(shù)組
  • 前端處理器通過處理器適配器包裝后執(zhí)行Handler對(duì)象组去。
  • 處理業(yè)務(wù)邏輯鞍陨。
  • Handler處理完業(yè)務(wù)邏輯,返回ModelAndView對(duì)象从隆,其中view是視圖名稱诚撵,不是真正的視圖對(duì)象。
  • 將ModelAndView返回給前端控制器广料。
  • 視圖解析器(ViewResolver)返回真正的視圖對(duì)象(View)。
  • (此時(shí)前端控制器中既有視圖又有Model對(duì)象數(shù)據(jù))前端控制器根據(jù)模型數(shù)據(jù)和視圖對(duì)象幼驶,進(jìn)行視圖渲染艾杏。
  • 返回渲染后的視圖(html/json/xml)返回。
  • 給用戶產(chǎn)生響應(yīng)盅藻。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末购桑,一起剝皮案震驚了整個(gè)濱河市畅铭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勃蜘,老刑警劉巖硕噩,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異缭贡,居然都是意外死亡炉擅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門阳惹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谍失,“玉大人,你說我怎么就攤上這事莹汤】煊悖” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵纲岭,是天一觀的道長抹竹。 經(jīng)常有香客問我,道長止潮,這世上最難降的妖魔是什么窃判? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮沽翔,結(jié)果婚禮上兢孝,老公的妹妹穿的比我還像新娘。我一直安慰自己仅偎,他們只是感情好跨蟹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著橘沥,像睡著了一般窗轩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上座咆,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天痢艺,我揣著相機(jī)與錄音,去河邊找鬼介陶。 笑死堤舒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哺呜。 我是一名探鬼主播舌缤,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了国撵?” 一聲冷哼從身側(cè)響起陵吸,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎介牙,沒想到半個(gè)月后壮虫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡环础,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年囚似,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喳整。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谆构,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出框都,到底是詐尸還是另有隱情搬素,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布魏保,位于F島的核電站熬尺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谓罗。R本人自食惡果不足惜粱哼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望檩咱。 院中可真熱鬧揭措,春花似錦、人聲如沸刻蚯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炊汹。三九已至躬充,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讨便,已是汗流浹背充甚。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霸褒,地道東北人伴找。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像废菱,于是被迫代替她去往敵國和親技矮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子眉反,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容