ContextLoaderListener的實(shí)現(xiàn)

ContextLoaderListener設(shè)計(jì)與實(shí)現(xiàn)

在web項(xiàng)目中途戒,Spring通過(guò)ContextLoaderListener監(jiān)聽(tīng)器來(lái)實(shí)現(xiàn)在web服務(wù)器啟動(dòng)時(shí)載入IOC容器愕乎。而ContextLoaderListener則通過(guò)使用ContextLoader來(lái)實(shí)現(xiàn)IOC容器的初始化工作肯骇。下面來(lái)看看ContextLoaderListener是如何實(shí)現(xiàn)的

源碼入口

對(duì)于 Spring 的ContextLoaderListener功能實(shí)現(xiàn)的分析爽篷,我們首先從 web.xml 開(kāi)始

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"
    xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext2.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   <!-- 配置DispatchcerServlet -->
      <servlet>
        <servlet-name>springDispatcherServlet</servlet-name>
         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
         <!-- 配置Spring mvc下的配置文件的位置和名稱 -->
         <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>classpath:applicationContext.xml</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
     </servlet>
     
     <servlet-mapping>
         <servlet-name>springDispatcherServlet</servlet-name>
         <url-pattern>/</url-pattern>
     </servlet-mapping>
</web-app>   

上面的配置可以看到毡庆,當(dāng)服務(wù)器啟動(dòng)時(shí)显拜,會(huì)觸發(fā)ContextLoaderListener監(jiān)聽(tīng)器,ContextLoaderListener將會(huì)加載classpath目錄下的applicationContext2.xml文件進(jìn)行解析靡努、注冊(cè)并注入bean坪圾。

ContextLoaderListener啟動(dòng)過(guò)程

當(dāng)服務(wù)器啟動(dòng)時(shí)【這里以Tomcat服務(wù)器為例】,會(huì)進(jìn)入Tomcat下的Bootstrap類(lèi)的main方法惑朦,并啟動(dòng)各個(gè)組件,具體的調(diào)用過(guò)程就不寫(xiě)了漓概,有空再寫(xiě)篇Tomcat服務(wù)器啟動(dòng)過(guò)程文章漾月。
Bootstrap類(lèi)的main方法:

  public static void main(String args[]) {
        if (daemon == null) {
                   Bootstrap bootstrap = new Bootstrap();
                   bootstrap.init();
                     daemon = bootstrap;
              }
        try {
            String command = "start";
            if (args.length > 0) {
                command = args[args.length - 1];
            }
          else if (command.equals("start")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();//這里開(kāi)始啟動(dòng)各個(gè)組件
            } 
    }   

最終調(diào)用到StandardContext類(lèi)的startInternal方法,這個(gè)方法比較長(zhǎng)胃珍,我就截關(guān)鍵部分的代碼:

    protected synchronized void startInternal() throws LifecycleException {
        //前面的代碼省略
            if (ok) {
                //這里開(kāi)始執(zhí)行監(jiān)聽(tīng)器的啟動(dòng)過(guò)程
                if (!listenerStart()) {
                    log.error( "Error listenerStart");
                    ok = false;
                }
            }
           //后面的代碼也省略了梁肿; 
       
    }

最后執(zhí)行所有實(shí)現(xiàn)了ServletContextListener監(jiān)聽(tīng)器的類(lèi)。
看下ContextLoaderListener的接口

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

所以將執(zhí)行ContextLoaderListener類(lèi)中的contextInitialized方法

@Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }

我們接著來(lái)看initWebApplicationContext(event.getServletContext());方法觅彰,這個(gè)將進(jìn)行Spring的IOC的解析吩蔑,注冊(cè),注入的過(guò)程
進(jìn)去這個(gè)initWebApplicationContext(event.getServletContext());方法
填抬,這個(gè)方法是在ContexLoader類(lèi)中實(shí)現(xiàn)的

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
          //如果容器已經(jīng)被初始化將拋出異常烛芬,也就是容器只能初始化一次
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException(
                    "Cannot initialize context because there is already a root application context present - " +
                    "check whether you have multiple ContextLoader* definitions in your web.xml!");
        }
        try {
        //如果容器為空,這里將創(chuàng)建一個(gè)容器飒责,也就是WebApplicationContext容器
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    
                    if (cwac.getParent() == null) {
                    
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
        //執(zhí)行解析赘娄、注冊(cè),注入過(guò)程
            configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }
//把新創(chuàng)建的容器存放到servletContext容器中   
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

            ClassLoader ccl = Thread.currentThread().getContextClassLoader();
            if (ccl == ContextLoader.class.getClassLoader()) {
                currentContext = this.context;
            }
            else if (ccl != null) {
                currentContextPerThread.put(ccl, this.context);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
                        WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
            }
            

            return this.context;
        }
        catch (RuntimeException ex) {
            logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
            throw ex;
        }
        catch (Error err) {
            logger.error("Context initialization failed", err);
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
            throw err;
        }
    }

從上面的代碼可以看出宏蛉,分為三部遣臼,首先創(chuàng)建容器,這個(gè)容器是WebApplicationContext容器拾并,然后通過(guò)容器來(lái)執(zhí)行bean的解析揍堰,注冊(cè)和注入過(guò)程,最后把WebApplicationContext容器存放到servletContext上下文中以備用嗅义。
 最后來(lái)看下容器進(jìn)行bean的解析屏歹、注冊(cè),注入過(guò)程吧
首先進(jìn)入configureAndRefreshWebApplicationContext方法

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            
            String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
            if (idParam != null) {
                wac.setId(idParam);
            }
            else {
                // Generate default id...
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                        ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }

        wac.setServletContext(sc);
//這里拿到web.xml配置文件中配置的applicationContext2.xml文件
        String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
        if (configLocationParam != null) {
            wac.setConfigLocation(configLocationParam);
        }

        
        ConfigurableEnvironment env = wac.getEnvironment();
        if (env instanceof ConfigurableWebEnvironment) {
            ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
        }

        customizeContext(sc, wac);
//關(guān)鍵步驟芥喇,這里將執(zhí)行bean的解析和注冊(cè)西采,注入過(guò)程
        wac.refresh();
    }

由于解析注冊(cè)注入過(guò)程比較復(fù)雜,以后有空再專門(mén)寫(xiě)一篇吧继控,先到這了械馆。

后記

這是我自己看源碼總結(jié)的胖眷,錯(cuò)誤之處請(qǐng)幫忙指出,大家一起進(jìn)步霹崎。

[朝陽(yáng)區(qū)尼克楊]
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處珊搀,謝謝啦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尾菇,一起剝皮案震驚了整個(gè)濱河市境析,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌派诬,老刑警劉巖劳淆,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異默赂,居然都是意外死亡沛鸵,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)缆八,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)曲掰,“玉大人,你說(shuō)我怎么就攤上這事奈辰±秆” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵奖恰,是天一觀的道長(zhǎng)吊趾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)房官,這世上最難降的妖魔是什么趾徽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮翰守,結(jié)果婚禮上孵奶,老公的妹妹穿的比我還像新娘。我一直安慰自己蜡峰,他們只是感情好了袁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著湿颅,像睡著了一般载绿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上油航,一...
    開(kāi)封第一講書(shū)人閱讀 51,488評(píng)論 1 302
  • 那天崭庸,我揣著相機(jī)與錄音,去河邊找鬼。 笑死怕享,一個(gè)胖子當(dāng)著我的面吹牛执赡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播函筋,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沙合,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了跌帐?” 一聲冷哼從身側(cè)響起首懈,我...
    開(kāi)封第一講書(shū)人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谨敛,沒(méi)想到半個(gè)月后究履,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佣盒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年挎袜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肥惭。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖紊搪,靈堂內(nèi)的尸體忽然破棺而出蜜葱,到底是詐尸還是另有隱情,我是刑警寧澤耀石,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布牵囤,位于F島的核電站,受9級(jí)特大地震影響滞伟,放射性物質(zhì)發(fā)生泄漏揭鳞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一梆奈、第九天 我趴在偏房一處隱蔽的房頂上張望野崇。 院中可真熱鬧,春花似錦亩钟、人聲如沸乓梨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扶镀。三九已至,卻和暖如春焰轻,著一層夾襖步出監(jiān)牢的瞬間臭觉,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝠筑,地道東北人狞膘。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像菱肖,于是被迫代替她去往敵國(guó)和親客冈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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