1. 程式人生 > >【Spring】- ContextLoaderListener 工作原理

【Spring】- ContextLoaderListener 工作原理

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

ContextLoaderListener:上下文載入器監聽器

作用:負責IOC容器的關閉\開啟工作

ContextLoaderListener 原始碼:

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 配置:

<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)方法

擴充套件:ServletContext物件的生成程式碼: Tomcate內部的StandardContext類(推薦書籍:How Tomcat Work)

 @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實際處理的

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

主要步驟:

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

web.xml配置自定義上下文方法: 補充:context-param:在Servlet容器啟動之後會被封裝進ServletContext物件中,引數值可以通過servletContext.getInitParameter("引數名")的形式獲取

<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介