注解驅(qū)動的springmvc加載過程源碼解析

按照servlet3.0規(guī)范的規(guī)定沉删,tomcat等web容器在啟動的時候需要查看META-INF/services目錄下的以javax.servlet.ServletContainerInitializer作為文件名的文件并加載文件中實現(xiàn)了ServletContainerInitializer接口的類

ServletContainerInitializer

spring-web的實現(xiàn)為SpringServletContainerInitializer:

WEB-INF/services/
SpringServletContainerInitializer

該類被@HandlesTypes(WebApplicationInitializer.class)所注解翻屈,所以該類的onStartup方法實現(xiàn)中可以以Set<Class<?>> webAppInitializerClasses參數(shù)接收到所有WebApplicationInitializer接口的實現(xiàn)類:

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

再看看WebApplicationInitializer的實現(xiàn)類有哪些:

WebApplicationInitializer的層級關系

下面看看SpringServletContainerInitializer.onStartup具體的邏輯(相關性不強的代碼我就不貼出來了照雁,主要看邏輯):

    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        List<WebApplicationInitializer> initializers = new LinkedList<>();
         //拿到所有WebApplicationInitializer的實現(xiàn)類,如果該類不是接口宪萄、抽象類那么反射創(chuàng)建該類實例
        if (webAppInitializerClasses != null) {
            for (Class<?> waiClass : webAppInitializerClasses) {
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)
                                ReflectionUtils.accessibleConstructor(waiClass).newInstance());
                    }
                    catch (Throwable ex) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
                    }
                }
            }
        }
        //排序后依次執(zhí)行onStartup方法
        AnnotationAwareOrderComparator.sort(initializers);
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }

根據(jù)上面代碼的分析SpringServletContainerInitializer并沒有執(zhí)行具體的springIOC容器的加載和DispatcherServlet的注冊州叠,而是把具體的加載邏輯委托給了WebApplicationInitializer的實現(xiàn)類旧困,但是WebApplicationInitializer的層級關系圖顯示印屁,spring框架除了提供了三個抽象實現(xiàn)外并沒有提供具體的實現(xiàn)類循捺,所以需要開發(fā)者自己提供斩例,我們先看最外層的封裝AbstractAnnotationConfigDispatcherServletInitializer的注釋(不喜歡英文的直接看后面翻譯):

/**

  • Base class for {@link org.springframework.web.WebApplicationInitializer}
  • implementations that register a
  • {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
  • configured with annotated classes, e.g. Spring's
  • {@link org.springframework.context.annotation.Configuration @Configuration} classes.
  • <p>Concrete implementations are required to implement {@link #getRootConfigClasses()}
  • and {@link #getServletConfigClasses()} as well as {@link #getServletMappings()}.
  • Further template and customization methods are provided by
  • {@link AbstractDispatcherServletInitializer}.
  • <p>This is the preferred approach for applications that use Java-based
  • Spring configuration.
    */
    大概意思是說:這個類是WebApplicationInitializer接口的基礎實現(xiàn)雄人,目的是通過使用諸如@Configuration注解的配置類來注冊DispatcherServlet(譯者:也包括root application context),繼承這個類的具體實現(xiàn)需要實現(xiàn)getRootConfigClasses念赶、getServletConfigClasses和getServletMappings三個方法础钠,并且繼承這個類是使用基于注解配置的推薦方式。
    通過上面提供的三個方法名我們可以推斷叉谜,只需要通過類似
AbstractAnnotationConfigDispatcherServletInitializer實現(xiàn)

這種方式就可以配置spring的root 容器和DispatcherServlet使用的mvc容器并配置DispatcherServlet的路徑映射信息了旗吁,相當簡單。

下面分析原理,再次熟悉一下層級關系:
->WebApplicationInitializer
->AbstractContextLoaderInitializer
->AbstractDispatcherServletInitializer
->AbstractAnnotationConfigDispatcherServletInitializer

直接看onStartup方法停局,onStartup只在AbstractContextLoaderInitializer和AbstractDispatcherServletInitializer中實現(xiàn)很钓,我們從外往里看
AbstractDispatcherServletInitializer:

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //調(diào)用父類的onStartup
        super.onStartup(servletContext);
        //注冊DispatcherServlet
        registerDispatcherServlet(servletContext);
    }

AbstractContextLoaderInitializer:

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //注冊ContextLoaderListener
        registerContextLoaderListener(servletContext);
    }

其實從類的名字上我們就可以猜出來他們各自完成了什么工作了香府,AbstractContextLoaderInitializer的作用是向servletContext中注冊ContextLoaderListener,這和我們使用xml配置方式加載springIOC容器的方式是一樣的,AbstractDispatcherServletInitializer負責注冊DispatcherServlet加載mvc容器码倦。

    protected void registerContextLoaderListener(ServletContext servletContext) {
        //創(chuàng)建根容器rootAppContext
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            //實例化ContextLoaderListener
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            //必要的話傳入contextInitializers
            listener.setContextInitializers(getRootApplicationContextInitializers());
            //注冊到servletContext中
            servletContext.addListener(listener);
        }
        else {
            logger.debug("No ContextLoaderListener registered, as " +
                    "createRootApplicationContext() did not return an application context");
        }
    }

關于上面創(chuàng)建根容器rootAppContext的方法createRootApplicationContext:

    protected WebApplicationContext createRootApplicationContext() {
        //獲取用戶定義的根容器配置類企孩,在我們的例子中是AppConfig
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            //僅僅將配置類注冊到根IOC容器中以供ContextLoaderListener使用,具體容器加載過程由ContextLoaderListener完成
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(configClasses);
            return rootAppContext;
        }
        else {
            return null;
        }
    }

ContextLoaderListener加載IOC容器的原理這里就不再贅述了袁稽。下面看DispatcherServlet的注冊過程:

protected void registerDispatcherServlet(ServletContext servletContext) {
        //獲得servlet名稱勿璃,固定值"dispatcher"
        String servletName = getServletName();
        //生成AnnotationConfigWebApplicationContext容器,并將配置類注冊進去(具體代碼看后面的代碼塊)
        WebApplicationContext servletAppContext = createServletApplicationContext();
        //實例化DispatcherServlet,傳入生成好的context容器
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        //必要的話傳入contextInitializers
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
        //將DispatcherServlet注冊到servletContext中
        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        //設置LoadOnStartup
        registration.setLoadOnStartup(1);
        //設置mapping
        registration.addMapping(getServletMappings());
        //設置異步支持
        registration.setAsyncSupported(isAsyncSupported());
        //注冊過濾器
        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }
          //自定義配置
        customizeRegistration(registration);
    }

createServletApplicationContext:

    protected WebApplicationContext createServletApplicationContext() {
            //實例化容器
        AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
            //獲取配置類(webConfig)
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            //注冊配置類
            servletAppContext.register(configClasses);
        }
        return servletAppContext;
    }

配置好DispatcherServlet后推汽,服務器web容器啟動時根據(jù)LoadOnStartup屬性會自動開始springmvc容器的加載過程补疑。

總結(jié)

web容器啟動->
注冊contextloaderlistener(此監(jiān)聽器為servletcontextlistener,在servletcontext初始化之后執(zhí)行內(nèi)部方法完成root context的初始化工作)
向web容器注冊dispatcherservlet,設置loadonstartup參數(shù)為1歹撒,即啟動時執(zhí)行init方法莲组,在init方法中獲取之前初始化完成的root context 完成web context的初始化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暖夭,一起剝皮案震驚了整個濱河市胁编,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鳞尔,老刑警劉巖嬉橙,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異寥假,居然都是意外死亡市框,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門糕韧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枫振,“玉大人,你說我怎么就攤上這事萤彩》嗦耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵雀扶,是天一觀的道長杖小。 經(jīng)常有香客問我,道長愚墓,這世上最難降的妖魔是什么予权? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮浪册,結(jié)果婚禮上扫腺,老公的妹妹穿的比我還像新娘。我一直安慰自己村象,他們只是感情好笆环,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布攒至。 她就那樣靜靜地躺著,像睡著了一般躁劣。 火紅的嫁衣襯著肌膚如雪嗓袱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天习绢,我揣著相機與錄音渠抹,去河邊找鬼。 笑死闪萄,一個胖子當著我的面吹牛梧却,可吹牛的內(nèi)容都是我干的烤黍。 我是一名探鬼主播觅捆,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嚷量!你這毒婦竟也來了圆裕?” 一聲冷哼從身側(cè)響起广鳍,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吓妆,沒想到半個月后赊时,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡行拢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年祖秒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舟奠。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡竭缝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沼瘫,到底是詐尸還是另有隱情抬纸,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布耿戚,位于F島的核電站湿故,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏溅话。R本人自食惡果不足惜晓锻,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望飞几。 院中可真熱鬧,春花似錦独撇、人聲如沸屑墨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卵史。三九已至战转,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間以躯,已是汗流浹背槐秧。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忧设,地道東北人刁标。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像址晕,于是被迫代替她去往敵國和親膀懈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

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