【Spring】- ContextLoaderListener 工做原理

ContextLoaderListener:上下文加載器監聽器web

做用:負責IOC容器的關閉\開啓工做spring

ContextLoaderListener 源碼:app

public class ContextLoaderListener extends ContextLoader implements ServletContextListener{
   public ContextLoaderListener() { }
   public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

web.xml 配置:ide

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

工做原理: ContextLoaderListener 實現ServletContextListener接口,ServletContextListener做爲ServeltContext的監聽器,當Servlet容器啓動的時候,Servlet容器會根據Context容器生成ServletContext對象並進行初始化,而後調用ServletContextListener進行事件監聽,所以ContextLoaderLister在Servlet容器實例化時會進行無參構造器的形式實例化,而後調用ServletContextListener的contextInitialized(ServletContextEvent event)方法this

擴展:ServletContext對象的生成代碼: Tomcate內部的StandardContext類(推薦書籍:How Tomcat Work)spa

@Override
public ServletContext getServletContext() {

    if (context == null) {
        context = new ApplicationContext(this);
        if (altDDName != null)
            context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
    }
    return (context.getFacade());
}

IOC容器的初始化流程: contextInitialized(ServletContextEvent event)方法分析線程

ContextLoaderListener的IOC容器初始化工做是交給其父類ContextLoader實際處理的debug

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		
    //根據ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性判斷上下文是否已經啓動
    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) {  //判斷上下文是否爲空,若是爲空則建立webApplicationContext
            this.context = createWebApplicationContext(servletContext);
        }
        
        //判斷是不是ConfigurableWebApplicationContext類型的上下文,若是是進行相關的上下文的初始化配置
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) { //判斷上下文是否激活:refresh方法
                
                //設置父類上下文
                if (cwac.getParent() == null) {
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                
                //webApplicationContext的初始化配置
                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;
    }
}

主要步驟:code

  1. 建立上下文:ContextLoader的createWebApplicationContext(ServletContext sc)方法處理,值得一提的是Spring支持自定義的上下文的功能,主要經過在web.xml文件中配置contextClass的形式提供,規定自定義實現的上下文必須實現ConfigurableWebApplicationContext接口,主要是方便WebApplicationContext的配置

web.xml配置自定義上下文方法: 補充:context-param:在Servlet容器啓動以後會被封裝進ServletContext對象中,參數值能夠經過servletContext.getInitParameter("參數名")的形式獲取xml

<context-param>
 <param-name>contextClass</param-name>
  <param-value> 
    自定義的上下文類的全路徑 
  </param-value>
</context-param>

判斷建立的上下文的方法:

protected Class<?> determineContextClass(ServletContext servletContext) {
		
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		
		if (contextClassName != null) {  //自定義的webApplicationContext
			try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}

//默認的webApplicationContext:XmlWebApplicationContext
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);
			}
		}
	}

工做流程:首先獲取ServletContext配置的contextClass初始化參數,若是存在則認定爲客戶使用自定義的上下文,而後使用類加載器加載,若是客戶未自定義上下文則使用默認的webApplicationContext:默認記錄文件:ContextLoader.properties

org.springframework.web.context.WebApplicationContext=
org.springframework.web.context.support.XmlWebApplicationContext

明顯Spring默認的webApplicationContext爲XmlWebApplicationContext類型

  1. 配置上下文

若是實現了ConfigurableWebApplicationContext接口,則調用接口的功能進行IOC容器的實例化工做:例如webAplicationContext的惟一標識(判斷IOC容器是否啓動),設置父上下文等

  1. 將線程和應用上下文綁定
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);
			}

流程: ServletContext首先存放IOC容器已經初始化的標識,而後經過比較線程上下文的類加載器和類自己的類加載器,判斷是否處於同一個線程,若是不是則綁定線程和上下文對象(經過綁定線程類加載器形式綁定),綁定關係維護在currentContextPerThread的Map中,通過上述步驟就完成了IOC容器的全部準備工做,能夠提供IOC容器的服務

IOC容器關閉過程:

@Override
public void contextDestroyed(ServletContextEvent event) {		closeWebApplicationContext(event.getServletContext());
    ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

從代碼能夠看出IOC容器的關閉通過兩個步驟,

  1. 關閉webApplicationContext
  2. 清理webApplicationContext的相關資源

關閉容器:ContextLoader的closeWebApplicationContext(ServletContext servletContext)方法

public void closeWebApplicationContext(ServletContext servletContext) {
    servletContext.log("Closing Spring root WebApplicationContext");
    try {
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ((ConfigurableWebApplicationContext) this.context).close();
        }
    }
    finally {
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {  
            currentContext = null;
        }else if (ccl != null) {
            currentContextPerThread.remove(ccl);
        }
        servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        if (this.parentContextRef != null) {
            this.parentContextRef.release();
        }
    }
}

流程:按照標準的IOC流程關閉自己及關聯的IOC容器,將全部引用應用上下文的對象置空,ServletContext應用清除上下文啓動的標識

清理IOC相關資源:

static void cleanupAttributes(ServletContext sc) {
    Enumeration<String> attrNames = sc.getAttributeNames();
    while (attrNames.hasMoreElements()) {
        String attrName = attrNames.nextElement();
        if (attrName.startsWith("org.springframework.")) {
            Object attrValue = sc.getAttribute(attrName);
            if (attrValue instanceof DisposableBean) {
                try {
                    ((DisposableBean) attrValue).destroy();
                }
                catch (Throwable ex) {
                    logger.error("Couldn't invoke destroy method of attribute with name '" + attrName + "'", ex);
                }
            }
        }
    }
}

流程:主要是經過查找DisposableBean接口的Bean,調用其destroy()方法實現用戶自定義的Bean銷燬的功能,例如Bean銷燬時須要進行某些處理,能夠經過實現DisposableBean接口來實現該功能

相關文章
相關標籤/搜索