溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Spring源碼跟蹤之ContextLoaderListener

發(fā)布時(shí)間:2020-08-10 04:12:55 來(lái)源:網(wǎng)絡(luò) 閱讀:1178 作者:zangyanan2016 欄目:開(kāi)發(fā)技術(shù)

  首先我們來(lái)看一段web.xml中的配置:

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>

  有經(jīng)驗(yàn)的都知道上面一段配置是關(guān)于web集成Spring的,每個(gè)初次學(xué)習(xí)Spring的,心中都會(huì)疑問(wèn)為何如此配置,下面跟著我來(lái)理解為何如此配置。

       首先理解一下什么是listener?

       Listener 的作用非常類似于load-on-startup Servlet。用于在Web 應(yīng)用啟動(dòng)時(shí),啟動(dòng)某些后臺(tái)程序,這些后臺(tái)程序負(fù)責(zé)為系統(tǒng)運(yùn)行提供支持。

        Listener 與load-on-startup Servlet 的區(qū)別在于: Listener 的啟動(dòng)時(shí)機(jī)比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 規(guī)范之后才出現(xiàn)的。
使用Listener 只需要兩個(gè)步驟:
(1)創(chuàng)建Listener 實(shí)現(xiàn)類。
(2)在web.xml 文件中配置Listener。
        創(chuàng)建Listener 類必須實(shí)現(xiàn)ServletContextListener 接口,該接口包含兩個(gè)方法。
contextInitialized(ServletContextEvent sce): 啟動(dòng)Web 應(yīng)用時(shí),系統(tǒng)調(diào)用該Filter的方法。

contextDestroyed(ServletContextEvent sce): 關(guān)閉Web 應(yīng)用時(shí)候,系統(tǒng)調(diào)用Filter的方法。

先大致介紹一下ContextLoaderListener啟動(dòng)運(yùn)行過(guò)程:

簡(jiǎn)單介紹一下上圖的運(yùn)行流程:

①啟動(dòng)項(xiàng)目時(shí)觸發(fā)contextInitialized方法,該方法就做一件事:通過(guò)父類contextLoader的initWebApplicationContext方法創(chuàng)建Spring上下文對(duì)象。

②initWebApplicationContext方法做了三件事:創(chuàng)建WebApplicationContext;加載對(duì)應(yīng)的Spring文件創(chuàng)建里面的Bean實(shí)例;將WebApplicationContext放入ServletContext(就是Java Web的全局變量)中。

③createWebApplicationContext創(chuàng)建上下文對(duì)象,支持用戶自定義的上下文對(duì)象,但必須繼承自ConfigurableWebApplicationContext,而Spring MVC默認(rèn)使用ConfigurableWebApplicationContext作為ApplicationContext(它僅僅是一個(gè)接口)的實(shí)現(xiàn)。

④configureAndRefreshWebApplicationContext方法用于封裝ApplicationContext數(shù)據(jù)并且初始化所有相關(guān)Bean對(duì)象。它會(huì)從web.xml中讀取名為contextConfigLocation的配置,這就是spring xml數(shù)據(jù)源設(shè)置,然后放到ApplicationContext中,最后調(diào)用傳說(shuō)中的refresh方法執(zhí)行所有Java對(duì)象的創(chuàng)建。

⑤完成ApplicationContext創(chuàng)建之后就是將其放入ServletContext中,注意它存儲(chǔ)的key值常量。

 下面來(lái)看一下ContextLoaderListener的源碼:

public class ContextLoaderListener extends ContextLoader
    implements ServletContextListener{

    public ContextLoaderListener()
    {
    }

    public ContextLoaderListener(WebApplicationContext context)
    {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event)
    {
    //這個(gè)方法已經(jīng)廢棄,返回值為null
        contextLoader = createContextLoader();
        if(contextLoader == null)
        //因?yàn)镃ontextLoaderListerner繼承了ContextLoader,所以可把自身賦值給
            contextLoader = this;
        //接著就實(shí)例化webApplicationContext,由父類ContextLoader實(shí)現(xiàn)
        contextLoader.initWebApplicationContext(event.getServletContext());
    }

    protected ContextLoader createContextLoader()
    {
        return null;
    }

    public ContextLoader getContextLoader()
    {
        return contextLoader;
    }

    public void contextDestroyed(ServletContextEvent event)
    {
        if(contextLoader != null)
            contextLoader.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

    private ContextLoader contextLoader;
}

因?yàn)樗鼘?shí)現(xiàn)了ServletContextListener這個(gè)接口,在web.xml配置這個(gè)監(jiān)聽(tīng)器,啟動(dòng)容器時(shí),就會(huì)默認(rèn)執(zhí)行它實(shí)現(xiàn)的方法。在ContextLoaderListener中關(guān)聯(lián)了ContextLoader這個(gè)類,所以整個(gè)加載配置過(guò)程由ContextLoader來(lái)完成,ContextLoader是一個(gè)工具類,用來(lái)初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我們繼續(xù)追蹤initWebApplicationContext這個(gè)方法,我們發(fā)現(xiàn),原來(lái)ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默認(rèn)實(shí)現(xiàn)類)放在了ServletContext中,ServletContext也是一個(gè)“容器”,也是一個(gè)類似Map的結(jié)構(gòu),而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我們?nèi)绻褂肳ebApplicationContext則需要從ServletContext取出,Spring提供了一個(gè)WebApplicationContextUtils類,可以方便的取出WebApplicationContext,只要把ServletContext傳入就可以了。 

在創(chuàng)建ContextLoader,會(huì)執(zhí)行它的一段靜態(tài)代碼塊。

  static 
    {
        try
        {
         // 這里創(chuàng)建一個(gè)ClassPathResource對(duì)象,載入ContextLoader.properties,用于創(chuàng)建對(duì)應(yīng)的ApplicationContext容器  
        // 這個(gè)文件跟ContextLoader類在同一個(gè)目錄下,文件內(nèi)容如:  
        // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext  
        // 如此說(shuō)來(lái),spring默認(rèn)初始化的是XmlWebApplicationContext  
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", org/springframework/web/context/ContextLoader);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
        catch(IOException ex)
        {
            throw new IllegalStateException((new StringBuilder("Could not load 'ContextLoader.properties': ")).append(ex.getMessage()).toString());
        }
    }

下面我們主要看看ContextLoader這個(gè)類的initWebApplicationContext方法。

        //以下這段代碼主要判斷是否重復(fù)實(shí)例化的問(wèn)題,因?yàn)閷?shí)例化webApplicationContext后,會(huì)把它放到servletContext的一個(gè)屬性里,所以我們可以從servletContext的屬性取出webApplicationContext,如果不為空,則已經(jīng)實(shí)例化,接著就會(huì)拋出異常.
        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!");

這個(gè)地點(diǎn)是為什么Spring需要引入common-logging這個(gè)jar,不然會(huì)報(bào)錯(cuò)。

 logger = LogFactory.getLog(org/springframework/web/context/ContextLoader);

調(diào)用LogFactory.getLog()時(shí)發(fā)生的事情:

          (1).common-logging首先在CLASSPATH中查找commons-logging.properties文件。這個(gè)屬性文件至少定義org.apache.commons.logging.Log屬性,它的值應(yīng)該是上述任意Log接口實(shí)現(xiàn)的完整限定名稱。如果找到 org.apache.commons.logging.Log屬相,則使用該屬相對(duì)應(yīng)的日志組件。結(jié)束發(fā)現(xiàn)過(guò)程。

        (2).如果上面的步驟失?。ㄎ募淮嬖诨?qū)傧嗖淮嬖冢?,common-logging接著檢查系統(tǒng)屬性org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系統(tǒng)屬性,則使用該系統(tǒng)屬性對(duì)應(yīng)的日志組件。結(jié)束發(fā)現(xiàn)過(guò)程。
        (3).如果找不到org.apache.commons.logging.Log系統(tǒng)屬性,common-logging接著在CLASSPATH中尋找log4j的類。如果找到了就假定應(yīng)用要使用的是log4j。不過(guò)這時(shí)log4j本身的屬性仍要通過(guò)log4j.properties文件正確配置。結(jié)束發(fā)現(xiàn)過(guò)程。
        (4).如果上述查找均不能找到適當(dāng)?shù)腖ogging API,但應(yīng)用程序正運(yùn)行在JRE 1.4或更高版本上,則默認(rèn)使用JRE 1.4的日志記錄功能。結(jié)束發(fā)現(xiàn)過(guò)程。
        (5).最后,如果上述操作都失?。↗RE 版本也低于1.4),則應(yīng)用將使用內(nèi)建的SimpleLog。SimpleLog把所有日志信息直接輸出到System.err。結(jié)束發(fā)現(xiàn)過(guò)程。

        為了簡(jiǎn)化配置 commons-logging ,一般不使用 commons-logging 的配置文件,也不設(shè)置與 commons-logging 相關(guān)的系統(tǒng)環(huán)境變量,而只需將 Log4j 的 Jar 包放置到 classpash 中就可以了。這樣就很簡(jiǎn)單地完成了 commons-logging 與 Log4j 的融合。

接著我們可看到:

 //接著就會(huì)創(chuàng)建webApplicationContext
 if(context == null)
 context = createWebApplicationContext(servletContext);

跟進(jìn)createWebApplicationContext這個(gè)方法:

 protected WebApplicationContext createWebApplicationContext(ServletContext sc)
    {
    //獲取相應(yīng)的class,其實(shí)就是ConfigurableWebApplicationContext.class
        Class contextClass = determineContextClass(sc);
        //判斷是否為 ConfigurableWebApplicationContext.class或從它繼承
        if(!org/springframework/web/context/ConfigurableWebApplicationContext.isAssignableFrom(contextClass))
        {
        //若非ConfigurableWebApplicationContext.class或非其子類則拋出異常
            throw new ApplicationContextException((new StringBuilder("Custom context class [")).append(contextClass.getName()).append("] is not of type [").append(org/springframework/web/context/ConfigurableWebApplicationContext.getName()).append("]").toString());
        } else
        {
          //創(chuàng)建一個(gè)contextClass的實(shí)例對(duì)象返回,然后就是把context強(qiáng)制轉(zhuǎn)換為configrableWebApplicationContext,實(shí)例化spring容器
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            return wac;
        }
    }

 該方法首先判斷從web.xml文件的初始化參數(shù)CONTEXT_CLASS_PARAM(的定義為public static final String CONTEXT_CLASS_PARAM = "contextClass";)獲取的的類名是否存在,如果存在,則容器的Class;否則返回默認(rèn)的Class。如何獲取默認(rèn)的容器Class,注意看創(chuàng)建contextLoader時(shí)的代碼注釋就知道了,  由此看來(lái),spring不僅有默認(rèn)的applicationContext的容器類,還允許我們自定義applicationContext容器類,不過(guò)Spring不建義我們自定義applicationContext容器類。

 protected Class determineContextClass(ServletContext servletContext)
    {
        String contextClassName;
        //從servletContext讀取contextClassName
        contextClassName = servletContext.getInitParameter("contextClass");
        if(contextClassName == null)
        //否則創(chuàng)建默認(rèn)的容器的Class對(duì)象,即:org.springframework.web.context.support.XmlWebApplicationContext 
 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
         //如果從servletContext讀取到的contextClassName不為空,就返回對(duì)應(yīng)的class類
        return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
        ClassNotFoundException ex;
        ex;
        //如果找不到該類名的類就拋出異常
        throw new ApplicationContextException((new StringBuilder("Failed to load custom context class [")).append(contextClassName).append("]").toString(), ex);
        contextClassName = defaultStrategies.getProperty(org/springframework/web/context/WebApplicationContext.getName());
        return ClassUtils.forName(contextClassName, org/springframework/web/context/ContextLoader.getClassLoader());
        ex;
        throw new ApplicationContextException((new StringBuilder("Failed to load default context class [")).append(contextClassName).append("]").toString(), ex);
    }

接著看核心方法configureAndRefreshWebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
    {
        if(ObjectUtils.identityToString(wac).equals(wac.getId()))
        {
            String idParam = sc.getInitParameter("contextId");
            if(idParam != null)
                wac.setId(idParam);
            else
            if(sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5)
                wac.setId((new StringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getServletContextName())).toString());
            else
                wac.setId((new StringBuilder(String.valueOf(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX))).append(ObjectUtils.getDisplayString(sc.getContextPath())).toString());
        }
        ApplicationContext parent = loadParentContext(sc);
        wac.setParent(parent);
        //把servletContext放到webApplicationContext中,以后可以直接取出來(lái)用
        wac.setServletContext(sc);
        String initParameter = sc.getInitParameter("contextConfigLocation");
        if(initParameter != null)
            wac.setConfigLocation(initParameter);
         //用戶自己的一些設(shè)置
        customizeContext(sc, wac);
        //進(jìn)行bean加載
        wac.refresh();
    }

我們進(jìn)入webApplicationContext的refresh方法看一下

   public void refresh()
        throws BeansException, IllegalStateException
    {
        synchronized(startupShutdownMonitor)
        {
            prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try
            {
                postProcessBeanFactory(beanFactory);
                invokeBeanFactoryPostProcessors(beanFactory);
                registerBeanPostProcessors(beanFactory);
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
            catch(BeansException ex)
            {
                destroyBeans();
                cancelRefresh(ex);
                throw ex;
            }
        }
    }

這是一個(gè)同步的方法,這里最核心的方法就是obtainFreshBeanFactory方法。其他的方法都是對(duì)webApplicationContext和beanFactory做一些前后的裝飾和準(zhǔn)備。

 protected ConfigurableListableBeanFactory obtainFreshBeanFactory()
    {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if(logger.isDebugEnabled())
            logger.debug((new StringBuilder("Bean factory for ")).append(getDisplayName()).append(": ").append(beanFactory).toString());
        return beanFactory;
    }

這里的核心方法就是refreshBeanFactory();它負(fù)責(zé)生成BeanFactory并加載bean

protected final void refreshBeanFactory()
        throws BeansException
    {
    //判斷是否已經(jīng)存在beanFactory如果有則銷毀。
        if(hasBeanFactory())
        {
            destroyBeans();
            closeBeanFactory();
        }
        try
        {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);//用戶自己做一些設(shè)置
            loadBeanDefinitions(beanFactory);//這個(gè)方法很關(guān)鍵,負(fù)責(zé)加載所有的bean
            synchronized(beanFactoryMonitor)
            {
                this.beanFactory = beanFactory;
            }
        }
        catch(IOException ex)
        {
            throw new ApplicationContextException((new StringBuilder("I/O error parsing bean definition source for ")).append(getDisplayName()).toString(), ex);
        }
    }

然后我們進(jìn)入loadBeanDefinitions(beanFactory);看一下

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
        throws BeansException, IOException
    {
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

這里主要工作就是new一個(gè)XmlBeanDefinitionReader,給它設(shè)置environment,resourceLoader和entityResolver,注意一下由于webApplicationContext實(shí)現(xiàn)了ResouceLoader接口,所以它本身就是一個(gè)ResourceLoader.我們可以看到它并不自己去實(shí)現(xiàn)lobeanDefinitions方法,而是委托給XmlBeanDefinitionReader去實(shí)現(xiàn)。

 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader)
        throws IOException
    {
        String configLocations[] = getConfigLocations();
        if(configLocations != null)
        {
            String as[];
            int j = (as = configLocations).length;
            for(int i = 0; i < j; i++)
            {
                String configLocation = as[i];
                reader.loadBeanDefinitions(configLocation);
            }

        }
    }

這里就對(duì)configLocations進(jìn)行bean的加載,調(diào)用重載的方法

  public int loadBeanDefinitions(String location)
        throws BeanDefinitionStoreException
    {
        return loadBeanDefinitions(location, null);
    }



向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI