HI END


一种永不妥协,追求极致与完美的精神与态度。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

spring源码研究之IoC容器在web容器中初始化过程(转)

Posted on 2012-12-19 15:24  HI END  阅读(243)  评论(0编辑  收藏  举报

来源:http://ljbal.iteye.com/blog/497314

前段时间在公司做了一个项目,项目用了spring框架实现,WEB容器是Tomct 5,虽然说把项目做完了,但是一直对spring的IoC容器在web容器如何启动和起作用的并不清楚。所以就抽时间看一下spring的源代码,借此了解它的原理。

    我们知道,对于使用Spring的web应用,无须手动创建Spring容器,而是通过配置文件,声明式的创建Spring容器。因此在Web应用中创建Spring容器有如下两种方式:

    1. 直接在web.xml文件中配置创建Spring容器。

    2. 利用第三方MVC框架的扩展点,创建Spring容器。

    其实第一种方式是更加常见。为了让Spring容器随Web应用的启动而启动,有如下两种方式:

    1. 利用ServletContextListener实现。

    2. 利用load-on-startup Servlet实现。

    Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类可以作为Listener 使用,它会在创建时自动查找WEB-INF下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,则只需在web.xml文件中增加以下配置片段就可以了。

   

Java代码 复制代码 收藏代码
  1. <listener>   
  2.       <listener-class>   
  3.      org.springframework.web.context.ContextLoaderListener   
  4.       </listener-class>   
  5. </listener>  
<listener>
      <listener-class>
	 org.springframework.web.context.ContextLoaderListener
      </listener-class>
</listener>

    如果有多个配置文件需要载入,则考虑使用<context-param...>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contentConfigLocation的初始化参数。因此,配置<context-param...>时就指定参数名为contextConfigLocation。

    带多个配置文件的web.xml文件如下:

   

S代码 复制代码 收藏代码
  1. <context-param>     
  2.           <param-name>contextLoaderListener</param-name>   
  3.      <param-value>      
  4.                    WEB-INF/*.xml, classpath:spring/*.xml   
  5.                  </param-value>   
  6. </context-param>  
<context-param>  
          <param-name>contextLoaderListener</param-name>
	 <param-value>   
	               WEB-INF/*.xml, classpath:spring/*.xml
                 </param-value>
</context-param>
S代码 复制代码 收藏代码
  1. <listener>   
  2.       <listener-class>   
  3.      org.springframework.web.context.ContextLoaderListener   
  4.       </listener-class>   
  5. </listener>  
<listener>
      <listener-class>
	 org.springframework.web.context.ContextLoaderListener
      </listener-class>
</listener>

    多个配置文件之间用“,”隔开。

 

    下面我们来看它的具体实现过程是怎样的,首先我们从ContextLoaderListener入手,它的代码如下:

   

Java代码 复制代码 收藏代码
  1. public class ContextLoaderListener implements ServletContextListener    
  2. {   
  3.   
  4.     private ContextLoader contextLoader;   
  5.   
  6.   
  7.     /**  
  8.      * 这个方法就是用来初始化web application context的  
  9.      */  
  10.     public void contextInitialized(ServletContextEvent event)    
  11.                 {   
  12.         this.contextLoader = createContextLoader();   
  13.         this.contextLoader.initWebApplicationContext(event.getServletContext());   
  14.     }   
  15.   
  16.     /**  
  17.      * 创建一个contextLoader.  
  18.      * @return the new ContextLoader  
  19.      */  
  20.     protected ContextLoader createContextLoader()   
  21.                 {   
  22.         return new ContextLoader();   
  23.     }   
  24.      ................   
  25.               
  26. }  
public class ContextLoaderListener implements ServletContextListener 
{

	private ContextLoader contextLoader;


	/**
	 * 这个方法就是用来初始化web application context的
	 */
	public void contextInitialized(ServletContextEvent event) 
                {
		this.contextLoader = createContextLoader();
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

	/**
	 * 创建一个contextLoader.
	 * @return the new ContextLoader
	 */
	protected ContextLoader createContextLoader()
                {
		return new ContextLoader();
	}
     ................
           
}

   我们看到初始化web application context的时候,首先通过new ContextLoader()创建一个contextLoader,

   new ContextLoader()具体做了什么事呢?ContextLoader的代码片段:

   

Java代码 复制代码 收藏代码
  1. static {   
  2.     try {   
  3.         // 这里创建一个ClassPathResource对象,载入ContextLoader.properties,用于创建对应的ApplicationContext容器   
  4.         // 这个文件跟ContextLoader类在同一个目录下,文件内容如:   
  5.         // org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext   
  6.         // 如此说来,spring默认初始化的是XmlWebApplicationContext   
  7.         ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);   
  8.         // 得到一个Properties对象,后面根据类名来创建ApplicationContext容器   
  9.         defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);   
  10.            }   
  11.            catch (IOException ex) {   
  12.          throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());   
  13.            }   
  14.             }  
static {
	try {
		// 这里创建一个ClassPathResource对象,载入ContextLoader.properties,用于创建对应的ApplicationContext容器
		// 这个文件跟ContextLoader类在同一个目录下,文件内容如:
		// org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
		// 如此说来,spring默认初始化的是XmlWebApplicationContext
		ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
		// 得到一个Properties对象,后面根据类名来创建ApplicationContext容器
		defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
	       }
	       catch (IOException ex) {
		 throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
	       }
            }

    代码注释里面已经说得很清楚了,很容易理解吧?嘿嘿......

    再下来我们再看一下initWebApplicationContext方法的实现过程:

   

Java代码 复制代码 收藏代码
  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext)   
  2.             throws IllegalStateException, BeansException {   
  3.   
  4.         // 从servletContext中获取ApplicationContext容器;如果已经存在,则提示初始化容器失败,检查web.xml文件中是否定义有多个容器加载器   
  5.         // ServletContext接口的简述:public interface ServletContext   
  6.         // 定义了一系列方法用于与相应的servlet容器通信,比如:获得文件的MIME类型,分派请求,或者是向日志文件写日志等。   
  7.         // 每一个web-app只能有一个ServletContext,web-app可以是一个放置有web application 文件的文件夹,也可以是一个.war的文件。   
  8.         // ServletContext对象包含在ServletConfig对象之中,ServletConfig对象在servlet初始化时提供servlet对象。   
  9.   
  10.         if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {   
  11.             throw new IllegalStateException(   
  12.                     "Cannot initialize context because there is already a root application context present - " +   
  13.                     "check whether you have multiple ContextLoader* definitions in your web.xml!");   
  14.         }   
  15.   
  16.         servletContext.log("Initializing Spring root WebApplicationContext");   
  17.         if (logger.isInfoEnabled()) {   
  18.             logger.info("Root WebApplicationContext: initialization started");   
  19.         }   
  20.         long startTime = System.currentTimeMillis();   
  21.   
  22.         try {   
  23.             // Determine parent for root web application context, if any.   
  24.             // 获取父容器   
  25.             ApplicationContext parent = loadParentContext(servletContext);   
  26.   
  27.             // Store context in local instance variable, to guarantee that   
  28.             // it is available on ServletContext shutdown.   
  29.             // 创建ApplicationContext容器   
  30.             this.context = createWebApplicationContext(servletContext, parent);   
  31.             // 把容器放入到servletContext中   
  32.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);   
  33.   
  34.             if (logger.isDebugEnabled()) {   
  35.                 logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +   
  36.                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");   
  37.             }   
  38.             if (logger.isInfoEnabled()) {   
  39.                 long elapsedTime = System.currentTimeMillis() - startTime;   
  40.                 logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");   
  41.             }   
  42.   
  43.             return this.context;   
  44.         }   
  45.         catch (RuntimeException ex) {   
  46.             logger.error("Context initialization failed", ex);   
  47.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);   
  48.             throw ex;   
  49.         }   
  50.         catch (Error err) {   
  51.             logger.error("Context initialization failed", err);   
  52.             servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);   
  53.             throw err;   
  54.         }   
  55.     }  
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
			throws IllegalStateException, BeansException {

		// 从servletContext中获取ApplicationContext容器;如果已经存在,则提示初始化容器失败,检查web.xml文件中是否定义有多个容器加载器
		// ServletContext接口的简述:public interface ServletContext
		// 定义了一系列方法用于与相应的servlet容器通信,比如:获得文件的MIME类型,分派请求,或者是向日志文件写日志等。
		// 每一个web-app只能有一个ServletContext,web-app可以是一个放置有web application 文件的文件夹,也可以是一个.war的文件。
		// ServletContext对象包含在ServletConfig对象之中,ServletConfig对象在servlet初始化时提供servlet对象。

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

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

		try {
			// Determine parent for root web application context, if any.
			// 获取父容器
			ApplicationContext parent = loadParentContext(servletContext);

			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			// 创建ApplicationContext容器
			this.context = createWebApplicationContext(servletContext, parent);
			// 把容器放入到servletContext中
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 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;
		}
	}

 

    从上面的代码可以看出,我们创建好的applicationContext容器会放在servletContext中。servletContext是什么  呢?

    在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。

    从initWebApplicationContext中可以看到真正创建applicationContext容器是由createWebApplicationContext方法来实现的,它的代码如下:

   

Java代码 复制代码 收藏代码
  1. protected WebApplicationContext createWebApplicationContext(   
  2.             ServletContext servletContext, ApplicationContext parent) throws BeansException    
  3.     {   
  4.         // 首先决定要创建的applicationContext容器的类   
  5.         Class contextClass = determineContextClass(servletContext);   
  6.         // 如果获取到的类不是ConfigurableWebApplicationContext类型的,则创建容器失败,所以这里创建的容器必须是ConfigurableWebApplicationContext类型的   
  7.         if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass))    
  8.         {   
  9.             throw new ApplicationContextException("Custom context class [" + contextClass.getName() +   
  10.                     "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");   
  11.         }   
  12.   
  13.         // 实例化spring容器   
  14.         ConfigurableWebApplicationContext wac =   
  15.                 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);   
  16.         wac.setParent(parent);   
  17.         wac.setServletContext(servletContext);   
  18.         // 获取contextConfigLocation初始化参数,该参数记录的是需要载入的多个配置文件(即定义bean的配置文件)   
  19.         String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);   
  20.         if (configLocation != null)    
  21.         {   
  22.             wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,   
  23.                     ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));   
  24.         }   
  25.   
  26.         wac.refresh();   
  27.         return wac;   
  28.     }  
protected WebApplicationContext createWebApplicationContext(
			ServletContext servletContext, ApplicationContext parent) throws BeansException 
	{
		// 首先决定要创建的applicationContext容器的类
		Class contextClass = determineContextClass(servletContext);
		// 如果获取到的类不是ConfigurableWebApplicationContext类型的,则创建容器失败,所以这里创建的容器必须是ConfigurableWebApplicationContext类型的
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) 
		{
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}

		// 实例化spring容器
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
		wac.setParent(parent);
		wac.setServletContext(servletContext);
		// 获取contextConfigLocation初始化参数,该参数记录的是需要载入的多个配置文件(即定义bean的配置文件)
		String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocation != null) 
		{
			wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,
					ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
		}

		wac.refresh();
		return wac;
	}

    createWebApplicationContext方法实现步骤为:

    1. 首先决定要创建的applicationContext容器的类
    2. 实例化applicationContext容器

    但它是如何决定要创建的容器类呢?我们看一下determineContextClass方法:

   

Java代码 复制代码 收藏代码
  1. protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException    
  2.     {   
  3.         // 从web.xml中获取需要初始化的容器的类名   
  4.         String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);   
  5.         // 如果获取到的类名不为空,则创建该容器的Class对象   
  6.         if (contextClassName != null)    
  7.         {   
  8.             try {   
  9.                 return ClassUtils.forName(contextClassName);   
  10.             }   
  11.             catch (ClassNotFoundException ex) {   
  12.                 throw new ApplicationContextException(   
  13.                         "Failed to load custom context class [" + contextClassName + "]", ex);   
  14.             }   
  15.         }   
  16.         // 否则创建默认的容器的Class对象,即:org.springframework.web.context.support.XmlWebApplicationContext   
  17.         // 在创建ContextLoader时,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);这句代码已经准备好默认的容器类   
  18.         else    
  19.         {   
  20.             contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());   
  21.             try    
  22.             {   
  23.                 return ClassUtils.forName(contextClassName);   
  24.             }   
  25.             catch (ClassNotFoundException ex)    
  26.             {   
  27.                 throw new ApplicationContextException(   
  28.                         "Failed to load default context class [" + contextClassName + "]", ex);   
  29.             }   
  30.         }   
  31.     }  
protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException 
	{
		// 从web.xml中获取需要初始化的容器的类名
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		// 如果获取到的类名不为空,则创建该容器的Class对象
		if (contextClassName != null) 
		{
			try {
				return ClassUtils.forName(contextClassName);
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		// 否则创建默认的容器的Class对象,即:org.springframework.web.context.support.XmlWebApplicationContext
		// 在创建ContextLoader时,defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);这句代码已经准备好默认的容器类
		else 
		{
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try 
			{
				return ClassUtils.forName(contextClassName);
			}
			catch (ClassNotFoundException ex) 
			{
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

 

    该方法首先判断从web.xml文件的初始化参数CONTEXT_CLASS_PARAM(的定义为public static final String CONTEXT_CLASS_PARAM = "contextClass";)获取的的类名是否存在,如果存在,则容器的Class;否则返回默认的

Class。如何获取默认的容器Class,注意看创建contextLoader时的代码注释就知道了。

    由此看来,spring不仅有默认的applicationContext的容器类,还允许我们自定义applicationContext容器类,不过Spring不建义我们自定义applicationContext容器类。

   

 

   好了,这就是spring的IoC容器在web容器如何启动和起作用的全部过程。细心的朋友可以看出创建applicationContext容器的同时会初始化配置文件中定义的bean类,createWebApplicationContext方法中的wac.refresh();这段代码就是用来初始化配置文件中定义的bean类的。它具体的实现过程现在还没完全搞清楚,等搞清楚了再跟大家分享!