spring源碼研究之IoC容器在web容器中初始化過程


轉載自 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>  

    如果有多個配置文件需要載入,則考慮使用<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>  
S代碼   收藏代碼
  1. <listener>  
  2.       <listener-class>  
  3.      org.springframework.web.context.ContextLoaderListener  
  4.       </listener-class>  
  5. </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. }  

   我們看到初始化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.             }  

    代碼注釋里面已經說得很清楚了,很容易理解吧?嘿嘿......

    再下來我們再看一下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.     }  

 

    從上面的代碼可以看出,我們創建好的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.     }  

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

 

    該方法首先判斷從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類的。它具體的實現過程現在還沒完全搞清楚,等搞清楚了再跟大家分享!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM