ContextLoaderListener---WebApplicationContext建立過程

ContextLoaderListener

Servlet容器實例化ContextLoaderListenerhtml

Servlet容器會實例化一個ContextLoaderListener前端

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

該類繼承ContextLoader,實現了ServletContextListener接口,使之具備listener功能java

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

ContextLoaderListener 實現了ServletContextListener接口web

public interface ServletContextListener extends EventListener {

    /**
     * web應用初始化開始的時候,收到通知, 全部的ServletContextListeners都會
     * 收到通知,在任何filter或者servlet以前
     */
    public void contextInitialized(ServletContextEvent sce);

    /**
     * ServletContext準備關閉的時候,會調用該方法;
     * 在contextDestroyed調用前,全部的servlet和filter都已經銷燬了
     */
    public void contextDestroyed(ServletContextEvent sce);
}
/**
     * Initialize the root web application context.
     */
    public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

    ...

    /**
     * Close the root web application context.
     */
    public void contextDestroyed(ServletContextEvent event) {
        if (this.contextLoader != null) {
            this.contextLoader.closeWebApplicationContext(event.getServletContext());
        }
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
  1. ServletContextListener擴展了ContextLoader,使之有Listener功能
  2. public void contextInitialized(ServletContextEvent sce);方法中,獲取ServletContext
  3. public void contextDestroyed(ServletContextEvent event);方法中,釋放資源。

流程圖描述該過程spring

Servlet容器加載實例化ContextLoaderListener調用contextInitialized(ServletContextEvent sce)Endmvc

contextInitialized方法內部實現app

/**
     * Initialize the root web application context.
     */
    public void contextInitialized(ServletContextEvent event) {
    //createContextLoader方法已經棄用,返回永遠是null
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
        //調用父類initWebApplicationContext方法,傳入ServletContext
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }
    /**
     * Create the ContextLoader to use. Can be overridden in subclasses.
     * @return the new ContextLoader
     * @deprecated in favor of simply subclassing ContextLoaderListener itself
     * (which extends ContextLoader, as of Spring 3.0)
     */
    @Deprecated
    protected ContextLoader createContextLoader() {
        return null;
    }

關鍵代碼: 
this.contextLoader.initWebApplicationContext(event.getServletContext());框架

獲得信息: 
1. 主要初始化任務在initWebApplicationContext中實現.ide

ContextLoader學習

1. 初始化ApplicationContext — initWebApplicationContext

關鍵代碼: 
1. this.context = createWebApplicationContext(servletContext); 
2. servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 
3. configureAndRefreshWebApplicationContext(cwac, servletContext);

獲得信息: 
1. 該代碼建立了WebApplicationContext 
2. 這個源代碼很長(- -) 
3. 關鍵代碼在createWebApplicationContext(servletContext);裏面 
4. configureAndRefreshWebApplicationContext(cwac, servletContext);建立以後的配置工做,這個也很重要,之後再說,這裏直說建立Context過程。

代碼

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        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!");
        }

        Log logger = LogFactory.getLog(ContextLoader.class);
        servletContext.log("Initializing Spring root WebApplicationContext");
        if (logger.isInfoEnabled()) {
            logger.info("Root WebApplicationContext: initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            // Store context in local instance variable, to guarantee that
            // it is available on ServletContext shutdown.
            if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                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 ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    //此處也很重要
                    configureAndRefreshWebApplicationContext(cwac, 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 + "]");
            }
            if (logger.isInfoEnabled()) {
                long elapsedTime = System.currentTimeMillis() - startTime;
                logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
            }

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

2. 建立ApplicationContext — createWebApplicationContext

關鍵代碼: 
1. Class<?> contextClass = determineContextClass(sc); 
2. return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

獲得信息: 
1. 根據傳入的sevletContext,返回使用WebApplicationContext接口的哪個實現類,默認是XmlWebApplicationContext 
2. 使用BeanUtils實例化該類

源代碼

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

3. 決定使用哪一個WebApplicationContext?—determineContextClass

關鍵代碼: 
1. String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); 
2. public static final String CONTEXT_CLASS_PARAM = "contextClass"; 
3. return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());

獲得信息: 
1. 從Servlet InitParam中獲取的contextClassName,決定到底使用WebApplicationContext接口的哪個實現類。 
2. 若是你的web.xml中定義了以下片斷,會使用你本身的WebApplicationContext (默認」contextClass」),不然就使用XmlWebApplicationContext 
3. 利用反射獲得Class

web.xml

<context-param>
    <param-name>contextClass</param-name>
    <param-value>Your ContextClass</param-value>
</context-param>

代碼

protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load default context class [" + contextClassName + "]", ex);
            }
        }
    }

總結

配置web.xml

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
  1. Servlet容器實例化ContextLoaderListener,並通知其調用initWebApplicationContext
  2. ContextLoaderListener調用createWebApplicationContext根據ServletContext建立出WebApplicationContext
  3. 調用configureAndRefreshWebApplicationContext進行接下來的操做

完全暈了,後面的仍是畫個圖吧

這裏寫圖片描述

 

2:自我分析-Spring IOC在Web應用的啓動和銷燬

  Spring IOC容器經過ServletContextListener對servlet容器的生命週期監聽,從而實現了IOC的啓動和銷燬。

    注意:

1.分析框架代碼時,要常使用類繼承、調用關係等快捷鍵,能夠更高效的學習,快捷鍵能夠設置成你習慣的按鍵;

2.本文重在怎麼自我分析框架代碼,因此對其中解析需本身實際跟蹤代碼實踐方可;

3.spring源代碼版本 spring-framework-3.2.1.RELEASE。

 

 

預覽

javax.servlet.ServletContext,Servlet容器接口。

javax.servlet.ServletContextListener,Servlet容器生命週期監聽接口。

org.springframework.web.context.ContextLoaderListener,Spring IOC容器生命週期監聽類。

org.springframework.web.context.ContextLoader,Spring IOC容器啓動和銷燬。

 

配置-監聽ServletContext生命週期

在web.xml中spring配置了對ServletContext生命週期的監聽,當Web容器啓動和銷燬時,觸發Spring定義的IOC容器的啓動和銷燬,具體配置以下:

[html] view plain copy

 print?

  1. <listener>  
  2.         <listener-class>  
  3.             org.springframework.web.context.ContextLoaderListener  
  4.         </listener-class>  
  5.     </listener>  

 

入口-ContextLoaderListener對Spring IOC初始化和銷燬

當web容器啓動時會觸發contextInitialized方法對spring ioc容器進行初始化,銷燬時會觸發contextDestroyed方法對spring ioc容器進行銷燬,ServletContextListener接口以下:

 

  1. public void contextInitialized ( ServletContextEvent sce );  // ServletContext啓動時觸發  
  2.   
  3. public void contextDestroyed ( ServletContextEvent sce );    // ServletContext銷燬時觸發  

 

 

Spring IOC啓動

spring ioc容器初始化具體代碼以下:

  1. org.springframework.web.context.ContextLoaderListener  
  2.       
  3.     public void contextInitialized(ServletContextEvent event) {  
  4.         this.contextLoader = createContextLoader();  
  5.         if (this.contextLoader == null) {  
  6.             this.contextLoader = this;  
  7.         }  
  8.         // 對spring ioc容器進行初始化  
  9.         this.contextLoader.initWebApplicationContext(event.getServletContext());  
  10.     }  

 

IOC容器的初始化是由ContextLoader類執行:

  1. org.springframework.web.context.ContextLoader  
  2.       
  3.     public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {  
  4.         // 是否已經加載IOC容器  
  5.         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
  6.             throw new IllegalStateException(  
  7.                     "Cannot initialize context because there is already a root application context present - " +  
  8.                     "check whether you have multiple ContextLoader* definitions in your web.xml!");  
  9.         }  
  10.   
  11.         Log logger = LogFactory.getLog(ContextLoader.class);  
  12.         servletContext.log("Initializing Spring root WebApplicationContext");  
  13.         if (logger.isInfoEnabled()) {  
  14.             logger.info("Root WebApplicationContext: initialization started");  
  15.         }  
  16.         long startTime = System.currentTimeMillis();  
  17.   
  18.         try {  
  19.             // Store context in local instance variable, to guarantee that  
  20.             // it is available on ServletContext shutdown.  
  21.             if (this.context == null) {  
  22.                 this.context = createWebApplicationContext(servletContext);  
  23.             }  
  24.             if (this.context instanceof ConfigurableWebApplicationContext) {  
  25.                 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;  
  26.                 if (!cwac.isActive()) {  
  27.                     // The context has not yet been refreshed -> provide services such as  
  28.                     // setting the parent context, setting the application context id, etc  
  29.                     if (cwac.getParent() == null) {  
  30.                         // The context instance was injected without an explicit parent ->  
  31.                         // determine parent for root web application context, if any.  
  32.                         ApplicationContext parent = loadParentContext(servletContext);  
  33.                         cwac.setParent(parent);  
  34.                     }  
  35.                     // 此方法是刷新初始化IOC容器的地方  
  36.                     configureAndRefreshWebApplicationContext(cwac, servletContext);  
  37.                 }  
  38.             }  
  39.             // IOC容器加載後,將IOC容器保存於ServletContext容器中  
  40.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);  
  41.   
  42.         ...  
  43.     }  

 

熟悉的refresh()方法的調用:

  1. org.springframework.web.context.ContextLoader    
  2.       
  3.     protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {  
  4.         if (ObjectUtils.identityToString(wac).equals(wac.getId())) {  
  5.             // The application context id is still set to its original default value  
  6.             // -> assign a more useful id based on available information  
  7.             String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);  
  8.             if (idParam != null) {  
  9.                 wac.setId(idParam);  
  10.             }  
  11.             else {  
  12.                 // Generate default id...  
  13.                 if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {  
  14.                     // Servlet <= 2.4: resort to name specified in web.xml, if any.  
  15.                     wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  16.                             ObjectUtils.getDisplayString(sc.getServletContextName()));  
  17.                 }  
  18.                 else {  
  19.                     wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +  
  20.                             ObjectUtils.getDisplayString(sc.getContextPath()));  
  21.                 }  
  22.             }  
  23.         }  
  24.   
  25.         wac.setServletContext(sc);  
  26.         String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM);  
  27.         if (initParameter != null) {  
  28.             wac.setConfigLocation(initParameter);  
  29.         }  
  30.         customizeContext(sc, wac);  
  31.           
  32.         // 熟悉的refresh()對Spring IOC容器進行加載,接下來的步驟就是 "自我分析-Spring IOC"文髒中的內容  
  33.         wac.refresh();  
  34.     }  


 

Spring IOC銷燬

對Spring IOC容器和其餘Spring環境信息進行銷燬:

  1. org.springframework.web.context.ContextLoaderListener  
  2.       
  3.     public void contextDestroyed(ServletContextEvent event) {  
  4.         if (this.contextLoader != null) {  
  5.             this.contextLoader.closeWebApplicationContext(event.getServletContext());  
  6.         }  
  7.         ContextCleanupListener.cleanupAttributes(event.getServletContext());  
  8.     }  

具體清理銷燬了spring的什麼東西,本身再跟蹤下代碼便可。

 

 

 

spring的啓動過程:

  1. 首先,對於一個web應用,其部署在web容器中,web容器提供其一個全局的上下文環境,這個上下文就是ServletContext,其爲後面的spring IoC容器提供宿主環境;

  2. 其次,在web.xml中會提供有contextLoaderListener。在web容器啓動時,會觸發容器初始化事件,此時contextLoaderListener會監聽到這個事件,其contextInitialized方法會被調用,在這個方法中,spring會初始化一個啓動上下文,這個上下文被稱爲根上下文,即WebApplicationContext,這是一個接口類,確切的說,其實際的實現類是XmlWebApplicationContext。這個就是spring的IoC容器,其對應的Bean定義的配置由web.xml中的context-param標籤指定。在這個IoC容器初始化完畢後,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE爲屬性Key,將其存儲到ServletContext中,便於獲取;

  3. 再次,contextLoaderListener監聽器初始化完畢後,開始初始化web.xml中配置的Servlet,這個servlet能夠配置多個,以最多見的DispatcherServlet爲例,這個servlet其實是一個標準的前端控制器,用以轉發、匹配、處理每一個servlet請求。DispatcherServlet上下文在初始化的時候會創建本身的IoC上下文,用以持有spring mvc相關的bean。在創建DispatcherServlet本身的IoC上下文時,會利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先從ServletContext中獲取以前的根上下文(即WebApplicationContext)做爲本身上下文的parent上下文。有了這個parent上下文以後,再初始化本身持有的上下文。這個DispatcherServlet初始化本身上下文的工做在其initStrategies方法中能夠看到,大概的工做就是初始化處理器映射、視圖解析等。這個servlet本身持有的上下文默認實現類也是mlWebApplicationContext。初始化完畢後,spring以與servlet的名字相關(此處不是簡單的以servlet名爲Key,而是經過一些轉換,具體可自行查看源碼)的屬性爲屬性Key,也將其存到ServletContext中,以便後續使用。這樣每一個servlet就持有本身的上下文,即擁有本身獨立的bean空間,同時各個servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定義的那些bean。

說完了spring上下文的初始化過程,這三個上下文的關係應該就瞭解了。如仍是不太清楚,我就心有餘而力不足了,只能自行看代碼去了。

相關文章
相關標籤/搜索