Spring代碼分析一:加載與初始化

轉載地址:http://www.cnblogs.com/bobzeng/articles/1877140.html

 http://www.360doc.com/content/10/1223/08/1720440_80574231.shtmlhtml

通常的Web項目都會在web.xml中加入Spring監聽器,內容以下:java

?
1
2
3
4
5
6
7
8
< listener >
         < listener-class >org.springframework.web.context.ContextLoaderListener</ listener-class >
</ listener >
 
< context-param >
         < param-name >contextConfigLocation</ param-name >
         < param-value >classpath*:applicationContext-struts.xml,classpath*:spring/applicationContext.xml</ param-value >
</ context-param >

咱們的問題是,Spring是什麼時候以及如何加載咱們的配置文件來初始化Bean工廠的,帶着這些問題,咱們展開研究:web

咱們先來看看web.xml中配置的監聽器的類,來回答咱們的問題,Spring是什麼時候來加載咱們的配置文件的:spring

org.springframework.web.context.ContextLoaderListener服務器

image

它繼承了javax.servlet.ServletContextListener接口。app

ServletContextListener是J2EE Servlet API中的一個標準接口,函數

它可以監聽ServletContext對象的生命週期,實際上就是監聽Web應用的生命週期。post

當Servlet容器啓動或終止Web應用時,會觸發ServletContextEvent事件,該事件由ServletContextListener來處理。this

 

這裏面有兩個方法咱們比較感興趣:spa

?
1
2
3
4
5
6
7
/**
  * Create the ContextLoader to use. Can be overridden in subclasses.
  * @return the new ContextLoader
  */
protected  ContextLoader createContextLoader() {
       return  new  ContextLoader();
}

這個方法構造一個默認的ContextLoader,ContextLoader能夠理解爲Spring上下文的加載器。之因此這樣去定義這樣一個類,是爲了開發人員進行重寫此方法來使用一個自定義的Spring上下文的加載器。

?
1
2
3
4
5
6
7
/**
  * Initialize the root web application context.
  */
public  void  contextInitialized(ServletContextEvent event) {
      this .contextLoader = createContextLoader();
      this .contextLoader.initWebApplicationContext(event.getServletContext());
}

這個方法很簡單,僅僅只是調用了createContextLoader()構造了ContextLoader,並調用其初始化方法。

由此,咱們能夠得出結論,Spring是在Web項目啓動時,經過ServletContextListener機制,來加載以及初始化Spring上下文的。

 

下面,咱們好好研究一下Spring是如何加載其上下文的:

咱們先定位ContextLoader類。

image

看看此類的initWebApplicationContext()方法(省略了不重要的語句)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
  * Initialize Spring's web application context for the given servlet context,
  * 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
  * @throws IllegalStateException if there is already a root application context present
  * @throws BeansException if the context failed to initialize
  * @see #CONTEXT_CLASS_PARAM
  * @see #CONFIG_LOCATION_PARAM
  */
public  WebApplicationContext initWebApplicationContext(ServletContext servletContext)
         throws  IllegalStateException, BeansException {
     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!" );
     }
     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.
         this .context = createWebApplicationContext(servletContext, parent);
         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .context);
         currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this .context);
 
         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 parent = loadParentContext(servletContext);

這個方法的用途主要是用來解決Spring共享環境的,即,若是咱們有多個WAR包部署在同一個服務器上,並且這些WAR都共享某一套業務邏輯層。如何共享一套業務邏輯包配置而不要每一個WAR都單獨配置,這時咱們就可能須要Spring的共享環境了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected  ApplicationContext loadParentContext(ServletContext servletContext) throws  BeansException {
     ApplicationContext parentContext = null ;
     // 從web.xml中讀取父工廠的配置文件,默認爲:"classpath*:beanRefContext.xml"
     String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
 
     // 從web.xml中讀取父類工廠的名稱
     String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
 
     if  (parentContextKey != null ) {
         // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml"
         BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
         this .parentContextRef = locator.useBeanFactory(parentContextKey);
         parentContext = (ApplicationContext) this .parentContextRef.getFactory();
     }
 
     return  parentContext;
}

如今咱們引入BeanFactoryLocator,它是Spring配置文件的一個定位器,Spring官方給它的定義是用來查找,使用和釋放一個BeanFactory或其子類的接口。下面咱們看看此圖:

image

ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);

是根據參數locatorFactorySelector去一個單例工廠中去拿一個對應的BeanFactoryLocator,也即,若是工廠中沒有對應於locatorFactorySelector的BeanFactoryLocator對象,那就返回一個新的BeanFactoryLocator實例(這裏是ContextSingletonBeanFactoryLocator的實例),不然,就從工廠裏取現有的BeanFactoryLocator對象。

ContextSingletonBeanFactoryLocator裏維護了一個靜態的Map對象instances,每次須要新增BeanFactoryLocator實例時都會更新這個Map對象,這個Map對象是以配置文件名爲KEY,BeanFactoryLocator對象爲值。緣由很簡單,就是但願同一個配置文件只被初始化一次。

若是沒有在web.xml中定義locatorFactorySelector這個參數,父環境的配置文件默認使用:"classpath*:beanRefContext.xml"

 

this.parentContextRef = locator.useBeanFactory(parentContextKey);

此方法定義在SingletonBeanFactoryLocator類中,一樣是一個單例工廠模式,判斷傳入的參數parentContextKey對應的BeanFactory是否有被初始化,通過上面的ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector)指定Spring父環境配置文件,這個方法判斷指定的父環境是否被初始化,若是有則返回,沒有就進行初始化。看看此方法的實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public  BeanFactoryReference useBeanFactory(String factoryKey) throws  BeansException {
     synchronized  ( this .bfgInstancesByKey) {
         BeanFactoryGroup bfg = (BeanFactoryGroup) this .bfgInstancesByKey.get( this .resourceLocation);
 
         if  (bfg != null ) {
             bfg.refCount++;
         } else  {
             // Create the BeanFactory but don't initialize it.
             BeanFactory groupContext = createDefinition( this .resourceLocation, factoryKey);
 
             // Record its existence now, before instantiating any singletons.
             bfg = new  BeanFactoryGroup();
             bfg.definition = groupContext;
             bfg.refCount = 1 ;
             this .bfgInstancesByKey.put( this .resourceLocation, bfg);
             this .bfgInstancesByObj.put(groupContext, bfg);
 
             // Now initialize the BeanFactory. This may cause a re-entrant invocation
             // of this method, but since we've already added the BeanFactory to our
             // mappings, the next time it will be found and simply have its
             // reference count incremented.
             try  {
                 initializeDefinition(groupContext);
             } catch  (BeansException ex) {
                 this .bfgInstancesByKey.remove( this .resourceLocation);
                 this .bfgInstancesByObj.remove(groupContext);
                 throw  new  BootstrapException( "Unable to initialize group definition. "  +
                     "Group resource name ["  + this .resourceLocation + "], factory key ["  + factoryKey + "]" , ex);
             }
         }
 
         try  {
             BeanFactory beanFactory = null ;
             if  (factoryKey != null ) {
                 beanFactory = (BeanFactory) bfg.definition.getBean(factoryKey, BeanFactory. class );
             } else  if  (bfg.definition instanceof  ListableBeanFactory) {
                 beanFactory = (BeanFactory) BeanFactoryUtils.beanOfType((ListableBeanFactory) bfg.definition, BeanFactory. class );
             } else  {
                 throw  new  IllegalStateException(
                     "Factory key is null, and underlying factory is not a ListableBeanFactory: "  + bfg.definition);
             }
             return  new  CountingBeanFactoryReference(beanFactory, bfg.definition);
          } catch  (BeansException ex) {
              throw  new  BootstrapException( "Unable to return specified BeanFactory instance: factory key ["  +
                     factoryKey + "], from group with resource name ["  + this .resourceLocation + "]" , ex);
          }
 
     }
}

此方法分爲兩做了兩件事,

第一,初始化上下文,主意這裏初始化的是從web.xml配置參數裏的Spring配置文件,也是上面講loadParentContext方法裏的

BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);

這句指定的參數。這裏初始化的是這個配置文件全部Bean。咱們指定的factoryKey對應的Bean也是其中之一。

 

第二,從已經初始化的Spring上下文環境中獲取Spring父環境。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
< beans
      < bean  id = "factoryBeanId"  class = "org.springframework.context.support.ClassPathXmlApplicationContext"
          < constructor-arg
                 < list
                      < value >sharebean.xml</ value
                 </ list
          </ constructor-arg
      </ bean >
      < bean  id = "factoryBeanId2"  class = "org.springframework.context.support.ClassPathXmlApplicationContext"
          < constructor-arg
                 < list
                      < value >sharebean2.xml</ value
                 </ list
          </ constructor-arg
      </ bean >
</ beans >
?
1
2
3
4
5
6
7
8
9
10
<!—========================= web.xml ========================= --> 
< context-param
         < param-name >locatorFactorySelector</ param-name
         < param-value >beanRefFactory.xml</ param-value
</ context-param
 
< context-param
         < param-name >parentContextKey</ param-name
         < param-value >factoryBeanId</
相關文章
相關標籤/搜索