一、Spring啓動入口ContextLoadListener

一、Spring與WEB容器整合   

web項目中,Spring啓動是在web.xml配置監聽器,如下所示: 

<!-- 配置Spring上下文監聽器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

 
  可以看看ContextLoaderListener類,它實現了Tomcat容器的ServletContextListener接口,所以它與普通的Servlet監聽是一樣的。同樣是重寫到兩個方法:contextInitialized()方法在web容器初始化時執行,contextDestroyed()方法在容器銷燬時執行。

    WEB容器啓動時會觸發初始化事件,ContextLoaderListener監聽到這個事件,其contextInitialized()方法會被調用,在這個方法中Spring會初始化一個根上下文,即WebApplicationContext。這是一個接口,其實際默認實現類是XmlWebApplicationContext。這個就是Spring IOC的容器,其對應bean定義的配置信息由web.xml中的context-param來指定

<!-- 配置Spring配置文件路徑 -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath*:applicationContext.xmlclasspath*:applicationContext-shiro.xml
        </param-value>
</context-param>

 在Spring IOC 容器啓動初始化完畢之後,會將其儲存到ServletContext中。形式如下:servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, WebApplicationContext context);



 
注:以前我們寫Servlet程序的時候,如果在Tomcat容器啓動的時候需要綁定一些參數或者做一些全局處理,那麼就需要實現ServletContextListener接口,在contextInitialized方法中編寫必要的代碼。然後在web.xml添加配置listener

 在ContextLoaderListener類中,只是實現了ServletContextListener提供的到兩個方法,Spring啓動主要的邏輯在父類ContextLoader的方法initWebApplicationContext實現。ContextLoaderListener的作用就是啓動web容器時自動裝配ApplicationContext的配置信息。更細化一點講,Spring的啓動過程其實就是Spring IOC容器的啓動過程。

 

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

 

 二、ContextLoader剖析

  從上一部分ContextLoaderListener類可以得知,ContextLoader實際執行Spring容器的初始化,Spring整個的配置工作都是在ContextLoader完成的,這裏參數ServletContextEvent由web容器提供,不做說明。ContextLoaderListener很好理解,所以我們主要看ContextLoader類。用Maven引入Spring的源碼,打開ContextLoader類,類註釋的第一行就是

/**
 * Performs the actual initialization work for the root application context.
  ......
**/

 用google翻譯:實際執行根應用上下文的初始化工作。這裏的根應用上下文就是上文所寫的WebApplicationContext。我們先看看ContextLoader的時序圖

 ContextLoader類initWebApplicationContext()方法

/**
 * Initialize Spring's web application context for the given servlet context,
 * using the application context provided at construction time, or creating a new one
 * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
 * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
 * @param servletContext current servlet context
 * @return the new WebApplicationContext
 * @see #ContextLoader(WebApplicationContext)
 * @see #CONTEXT_CLASS_PARAM
 * @see #CONFIG_LOCATION_PARAM
 */
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	//判斷ServletContext是否已經存在WebApplication,如果存在則拋出異常
	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);
		}
		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) {
					// 得到根上下文的父上下文,然後設置到根上下文 。一般的web項目parent爲空
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				//從web.xml加載參數,初始化跟上下文WebApplicationContext,創建bean工廠和bean對象。
				//這個過程比較麻煩,下一篇文章專門分析
				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;
	}
}

     

    在這個方法中ServletContext是由web容器監聽器(ContextLoaderListener)提供。首先判斷servlectContext中是否已經存在根上下文,如果存在,則拋出異常;否則通過createWebApplicationContext方法創建新的根上下文。然後通過loadParentContext()方法爲其設置父上下文。再通過configureAndRefreshWebApplicationContext爲根上下文構建bean工廠和bean對象。 最後把上下文存入servletContext,並且存入currentContextPerThread。至此初始化過程完畢,接下來可以獲取WebApplicationContext,進而用getBean("bean name")得到bean。

 

createWebApplicationContext()方法用來創建根上下文:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	//從web.xml配置的contextClass參數中獲取上下文類名,如果contextClass爲空,則使用默認的。
        //下文有說明
	Class<?> contextClass = determineContextClass(sc);
        //根上下文必須是ConfigurableWebApplicationContext的子類,否則拋出異常
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
				"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
	}
	//BeanUtils.instantiateClass工具方法,根據類名創建類
	return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
 

determineContextClass()方法返回根上下文的類名

protected Class<?> determineContextClass(ServletContext servletContext) {
	//從web.xml獲得參數contextClass,在一般的web項目中,此參數爲null
	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 {
		//獲得根上下文WebApplicationContext的默認實現類的類名,defaultStrategies是Properties類型,
		//在CotnextLoader類開頭static語句塊中初始化
		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);
		}
	}
}
 

WebApplicationContext默認實現類的類名獲取

static {
	try {
		//獲取當前包下面的ContextLoader.properties文件,文件內容是:
		//org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	}
	catch (IOException ex) {
		throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
	}
}
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章