中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring源碼跟蹤之ContextLoaderListener

發布時間:2020-08-10 04:12:55 來源:網絡 閱讀:1189 作者:zangyanan2016 欄目:開發技術

  首先我們來看一段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>

  有經驗的都知道上面一段配置是關于web集成Spring的,每個初次學習Spring的,心中都會疑問為何如此配置,下面跟著我來理解為何如此配置。

       首先理解一下什么是listener?

       Listener 的作用非常類似于load-on-startup Servlet。用于在Web 應用啟動時,啟動某些后臺程序,這些后臺程序負責為系統運行提供支持。

        Listener 與load-on-startup Servlet 的區別在于: Listener 的啟動時機比load-on-startup Servlet 早,只是Listener 是Servlet 2.3 規范之后才出現的。
使用Listener 只需要兩個步驟:
(1)創建Listener 實現類。
(2)在web.xml 文件中配置Listener。
        創建Listener 類必須實現ServletContextListener 接口,該接口包含兩個方法。
contextInitialized(ServletContextEvent sce): 啟動Web 應用時,系統調用該Filter的方法。

contextDestroyed(ServletContextEvent sce): 關閉Web 應用時候,系統調用Filter的方法。

先大致介紹一下ContextLoaderListener啟動運行過程:

簡單介紹一下上圖的運行流程:

①啟動項目時觸發contextInitialized方法,該方法就做一件事:通過父類contextLoader的initWebApplicationContext方法創建Spring上下文對象。

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

③createWebApplicationContext創建上下文對象,支持用戶自定義的上下文對象,但必須繼承自ConfigurableWebApplicationContext,而Spring MVC默認使用ConfigurableWebApplicationContext作為ApplicationContext(它僅僅是一個接口)的實現。

④configureAndRefreshWebApplicationContext方法用于封裝ApplicationContext數據并且初始化所有相關Bean對象。它會從web.xml中讀取名為contextConfigLocation的配置,這就是spring xml數據源設置,然后放到ApplicationContext中,最后調用傳說中的refresh方法執行所有Java對象的創建。

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

 下面來看一下ContextLoaderListener的源碼:

public class ContextLoaderListener extends ContextLoader
    implements ServletContextListener{

    public ContextLoaderListener()
    {
    }

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

    public void contextInitialized(ServletContextEvent event)
    {
    //這個方法已經廢棄,返回值為null
        contextLoader = createContextLoader();
        if(contextLoader == null)
        //因為ContextLoaderListerner繼承了ContextLoader,所以可把自身賦值給
            contextLoader = this;
        //接著就實例化webApplicationContext,由父類ContextLoader實現
        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;
}

因為它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啟動容器時,就會默認執行它實現的方法。在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成,ContextLoader是一個工具類,用來初始化WebApplicationContext,其主要方法就是initWebApplicationContext,我們繼續追蹤initWebApplicationContext這個方法,我們發現,原來ContextLoader是把WebApplicationContext(XmlWebApplicationContext是默認實現類)放在了ServletContext中,ServletContext也是一個“容器”,也是一個類似Map的結構,而WebApplicationContext在ServletContext中的KEY就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,我們如果要使用WebApplicationContext則需要從ServletContext取出,Spring提供了一個WebApplicationContextUtils類,可以方便的取出WebApplicationContext,只要把ServletContext傳入就可以了。 

在創建ContextLoader,會執行它的一段靜態代碼塊。

  static 
    {
        try
        {
         // 這里創建一個ClassPathResource對象,載入ContextLoader.properties,用于創建對應的ApplicationContext容器  
        // 這個文件跟ContextLoader類在同一個目錄下,文件內容如:  
        // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext  
        // 如此說來,spring默認初始化的是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這個類的initWebApplicationContext方法。

        //以下這段代碼主要判斷是否重復實例化的問題,因為實例化webApplicationContext后,會把它放到servletContext的一個屬性里,所以我們可以從servletContext的屬性取出webApplicationContext,如果不為空,則已經實例化,接著就會拋出異常.
        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!");

這個地點是為什么Spring需要引入common-logging這個jar,不然會報錯。

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

調用LogFactory.getLog()時發生的事情:

          (1).common-logging首先在CLASSPATH中查找commons-logging.properties文件。這個屬性文件至少定義org.apache.commons.logging.Log屬性,它的值應該是上述任意Log接口實現的完整限定名稱。如果找到 org.apache.commons.logging.Log屬相,則使用該屬相對應的日志組件。結束發現過程。

        (2).如果上面的步驟失敗(文件不存在或屬相不存在),common-logging接著檢查系統屬性org.apache.commons.logging.Log。如果找到org.apache.commons.logging.Log系統屬性,則使用該系統屬性對應的日志組件。結束發現過程。
        (3).如果找不到org.apache.commons.logging.Log系統屬性,common-logging接著在CLASSPATH中尋找log4j的類。如果找到了就假定應用要使用的是log4j。不過這時log4j本身的屬性仍要通過log4j.properties文件正確配置。結束發現過程。
        (4).如果上述查找均不能找到適當的Logging API,但應用程序正運行在JRE 1.4或更高版本上,則默認使用JRE 1.4的日志記錄功能。結束發現過程。
        (5).最后,如果上述操作都失敗(JRE 版本也低于1.4),則應用將使用內建的SimpleLog。SimpleLog把所有日志信息直接輸出到System.err。結束發現過程。

        為了簡化配置 commons-logging ,一般不使用 commons-logging 的配置文件,也不設置與 commons-logging 相關的系統環境變量,而只需將 Log4j 的 Jar 包放置到 classpash 中就可以了。這樣就很簡單地完成了 commons-logging 與 Log4j 的融合。

接著我們可看到:

 //接著就會創建webApplicationContext
 if(context == null)
 context = createWebApplicationContext(servletContext);

跟進createWebApplicationContext這個方法:

 protected WebApplicationContext createWebApplicationContext(ServletContext sc)
    {
    //獲取相應的class,其實就是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
        {
          //創建一個contextClass的實例對象返回,然后就是把context強制轉換為configrableWebApplicationContext,實例化spring容器
            ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
            return wac;
        }
    }

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

 protected Class determineContextClass(ServletContext servletContext)
    {
        String contextClassName;
        //從servletContext讀取contextClassName
        contextClassName = servletContext.getInitParameter("contextClass");
        if(contextClassName == null)
        //否則創建默認的容器的Class對象,即:org.springframework.web.context.support.XmlWebApplicationContext 
 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
         //如果從servletContext讀取到的contextClassName不為空,就返回對應的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中,以后可以直接取出來用
        wac.setServletContext(sc);
        String initParameter = sc.getInitParameter("contextConfigLocation");
        if(initParameter != null)
            wac.setConfigLocation(initParameter);
         //用戶自己的一些設置
        customizeContext(sc, wac);
        //進行bean加載
        wac.refresh();
    }

我們進入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;
            }
        }
    }

這是一個同步的方法,這里最核心的方法就是obtainFreshBeanFactory方法。其他的方法都是對webApplicationContext和beanFactory做一些前后的裝飾和準備。

 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();它負責生成BeanFactory并加載bean

protected final void refreshBeanFactory()
        throws BeansException
    {
    //判斷是否已經存在beanFactory如果有則銷毀。
        if(hasBeanFactory())
        {
            destroyBeans();
            closeBeanFactory();
        }
        try
        {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);//用戶自己做一些設置
            loadBeanDefinitions(beanFactory);//這個方法很關鍵,負責加載所有的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);
        }
    }

然后我們進入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一個XmlBeanDefinitionReader,給它設置environment,resourceLoader和entityResolver,注意一下由于webApplicationContext實現了ResouceLoader接口,所以它本身就是一個ResourceLoader.我們可以看到它并不自己去實現lobeanDefinitions方法,而是委托給XmlBeanDefinitionReader去實現。

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

        }
    }

這里就對configLocations進行bean的加載,調用重載的方法

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



向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

玉林市| 资阳市| 合肥市| 当雄县| 沽源县| 乳山市| 阜新| 平山县| 天津市| 丹凤县| 伊宁县| 四会市| 泗阳县| 武安市| 汶上县| 唐海县| 厦门市| 永州市| 乌海市| 鹤峰县| 理塘县| 宁晋县| 鱼台县| 库伦旗| 新安县| 潮州市| 西华县| 德清县| 祥云县| 子长县| 南皮县| 阿拉尔市| 广灵县| 大冶市| 湖北省| 甘孜县| 临沂市| 日照市| 浑源县| 巴里| 青海省|