01-SpringMVC啟動(dòng)過程分析

01-SpringMVC啟動(dòng)過程分析

我們使用Servlet3.0的方式來配置DispatcherServlet,同樣還是采用5.1.4.RELEASE版本的Spring。根據(jù)Spring官方文檔的介紹,我們可以利用下面這段代碼來配置


手動(dòng)配置Spring MVC

首先我來解釋一下,在Servlet3.0的時(shí)候提出了可以通過SPI的方式動(dòng)態(tài)注冊(cè)一個(gè)Servlet,Spring正是利用這一點(diǎn),通過SpringServletContainerInitializer來啟動(dòng)WebApplicationInitializer的實(shí)現(xiàn)類,因此我們可以通過實(shí)現(xiàn)WebApplicationInitializer接口,達(dá)到注冊(cè)DispatcherServlet給Servlet容器(本文使用Tomcat)。從圖中可以看到,一開始會(huì)實(shí)例化一個(gè)Spring容器(AnnotationConfigWebApplicationContext),并利用這個(gè)ApplicationContext來實(shí)例化DispatcherServlet。

首先我們來回顧一下Java中類的實(shí)例化過程

  • 初始化父類靜態(tài)變量(靜態(tài)變量,靜態(tài)代碼塊,靜態(tài)方法,需要注意的是main方法也是靜態(tài)的)
  • 初始化子類靜態(tài)變量(范圍同父類)
  • 初始化父類普通成員變量及方法
  • 調(diào)用父類構(gòu)造方法
  • 初始化子類普通成員變量及方法
  • 調(diào)用子類構(gòu)造方法
    那么,在實(shí)例化DispatcherServlet的時(shí)候也是嚴(yán)格按照這個(gè)順序來執(zhí)行的。我們按照這個(gè)思路來看看在不同階段都做了些什么。

DispatcherServlet的注冊(cè)過程

通過查看源碼我們可以看到,在DispatcherServlet中存在大量的靜態(tài)屬性和一個(gè)靜態(tài)代碼塊,他的父類FrameworkServlet中存在少量靜態(tài)屬性。這些屬性大多都是用來命名的常量,我們需要關(guān)注一下這個(gè)靜態(tài)代碼塊。


static.png

通過上圖可以看到,在這個(gè)靜態(tài)代碼塊中初始化了DispatcherServlet的默認(rèn)策略,這些策略在后面的onRefresh方法中會(huì)用到。
結(jié)束靜態(tài)資源的初始化之后最后會(huì)調(diào)用構(gòu)造方法,在構(gòu)造方法中會(huì)將傳進(jìn)來的ApplicationContext賦值給FrameworkServlet的webApplicationContext屬性。隨后會(huì)配置Servlet的名稱及URLMapping。
至此,DispatcherServlet就已經(jīng)被注冊(cè)到Servlet容器中去了,隨后在容器啟動(dòng)時(shí)會(huì)調(diào)用GenericServlet的init方法。這個(gè)在我們學(xué)習(xí)Servlet時(shí)應(yīng)該已經(jīng)學(xué)習(xí)過了。Spring通過重寫init方法來真正啟動(dòng)SpringMVC模塊。讓我們來關(guān)注init過程。

HttpServletBean關(guān)鍵代碼

HttpServletBean

通過上述代碼可以看到,Spring會(huì)先讀取Servlet配置的參數(shù),即Servlet的InitParam。如果沒有配置則不做操作,緊接著初始化ServletBean,但這是個(gè)抽象方法,具體實(shí)現(xiàn)在FrameworkServlet中,我們進(jìn)入子類中查看相關(guān)代碼

FrameworkServlet關(guān)鍵代碼

/**
     * 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;
        // 注意,此前我們?cè)谧?cè)DispatcherServlet時(shí)手動(dòng)創(chuàng)建了一個(gè)ApplicationContext并將它賦值給了webApplicationContext,因此這里不為空,若采用舊的web.xml方式注冊(cè)DispatcherServlet則這里為空
        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);
                }
            }
        }
        if (wac == null) {
            // No context instance was injected at construction time -> see if one
            // has been registered in the servlet context. If one exists, it is assumed
            // that the parent context (if any) has already been set and that the
            // user has performed any initialization such as setting the context id
            wac = findWebApplicationContext();
        }
        if (wac == null) {
            // No context instance is defined for this servlet -> create a local one
            // 如果是傳統(tǒng)方式會(huì)在這里創(chuàng)建ApplicatinContext
            wac = createWebApplicationContext(rootContext);
        }
        // 
        if (!this.refreshEventReceived) {
            // Either the context is not a ConfigurableApplicationContext with refresh
            // support or the context injected at construction time had already been
            // refreshed -> trigger initial onRefresh manually here.
            synchronized (this.onRefreshMonitor) {
                // 這里會(huì)調(diào)用DispatcherServlet的onRefresh方法,執(zhí)行初始化操作
                onRefresh(wac);
            }
        }
        
        return wac;
    }

onRefresh方法關(guān)鍵部分

onRefresh方法會(huì)利用之前提到的默認(rèn)策略(沒有自定義策略的前提下)來初始化相關(guān)功能,關(guān)鍵代碼如下:

/**
     * This implementation calls {@link #initStrategies}.
     */
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further strategy objects.
     */
    protected void initStrategies(ApplicationContext context) {
        // 初始化文件上傳解析器
        initMultipartResolver(context);
        // 初始化國際化解析器
        initLocaleResolver(context);
        // 初始化主題解析器
        initThemeResolver(context);
        // 初始化HandlerMapping,默認(rèn)是RequestMappgingHandlerMapping和SimpleUrlHandlerMapping,其中RequestMappingHandlerMapping是用來處理@RequestMapping的,而SimpleUrlHandlerMapping是用來維護(hù)Uri路徑模式的顯示注冊(cè)
        initHandlerMappings(context);
        // 注冊(cè)HandlerAdapter,默認(rèn)是RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter這三種
        initHandlerAdapters(context);
        // 初始化異常處理器
        initHandlerExceptionResolvers(context);
        // 初始化視圖名稱轉(zhuǎn)換器
        initRequestToViewNameTranslator(context);
        // 初始化視圖解析器
        initViewResolvers(context);
        // 初始化重定向?qū)傩怨芾砥?        initFlashMapManager(context);
    }

至此我們已經(jīng)可以了解了Spring MVC在啟動(dòng)的大概流程,大致了解Spring MVC都做了什么。后續(xù)會(huì)繼續(xù)分析請(qǐng)求到達(dá)Dispatcher Servlet后是如何找到對(duì)應(yīng)的controller以及參數(shù)封裝,最后再到數(shù)據(jù)返回等流程的詳細(xì)過程。
由于本人能力有限,難免會(huì)有表述不清或錯(cuò)誤的地方,還希望各位不吝指教,大家共同學(xué)習(xí),一起進(jìn)步。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容