Spring代码分析一:加载与初始化

一般的Web项目都会在web.xml中加入Spring监听器,内容如下:

?
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.xml中配置的监听器的类,来回答我们的问题,Spring是何时来加载我们的配置文件的:

org.springframework.web.context.ContextLoaderListener

image

它继承了javax.servlet.ServletContextListener接口。

ServletContextListener是J2EE Servlet API中的一个标准接口,

它能够监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期。

当Servlet容器启动或终止Web应用时,会触发ServletContextEvent事件,该事件由ServletContextListener来处理。

 

这里面有两个方法我们比较感兴趣:

?
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</ param-value >  
</ context-param >

这个一个典型的构造父环境的配置,web项目在启动的时候就会发现里面有Spring父环境的配置,那么Spring首先就会生成一个对应的配置文件为beanRefFactory.xml的BeanFactory(web.xml中的locatorFactorySelector参数指定),同时Spring在解析的时候,会发现factoryBeanId的配置同样为BeanFacotry(beanRefFactory.xml中factoryBeanId对应的Bean),所以Spring在拿父环境时就会写成:

beanFactory = (BeanFactory) bfg.definition.getBean(factoryKey, BeanFactory.class);

 

方法实现里引入了BeanFactoryGroup类。类的结构很简单

image

refCount:用来记录实例被外部引用的记数,当调用locator.useBeanFactory(parentContextKey)方法时,引用数就会加1,当调用CountingBeanFactoryReference#release方法时,引用数就会减1,当它变成0时,Spring就会释放掉它占用的内存,同时也会销毁掉它definition变量引用的BeanFactory。下次再调用locator.useBeanFactory(parentContextKey)就会重新初始化BeanFactory。说到release,请同学们参考ContextLoader中如下的两条语句:

// 在调用CountingBeanFactoryReference#release后,即使对象已经销毁,这个Map仍然可以返回locator对象。

BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);

// 如果对象已经销毁,再调用此方法会再一次初始化BeanFactory
this.parentContextRef = locator.useBeanFactory(parentContextKey);

 

bfgInstancesByKey:一个Map对象,以配置文件名为Key,配置文件解析后生成的BeanFactory构成的BeanFactoryGroup为值。

bfgInstancesByObj:一个Map对象,以BeanFactoryGroup.definitiion为Key,以BeanFactoryGroup为值。这个对象主要还是在CountingBeanFactoryReference#release时使用。

 

 

下面,我看再看看另一个地方:

?
1
2
3
4
5
6
7
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(); 
}
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);

上面这句仅仅是做了如下工作:

?
1
2
3
4
5
6
BeanFactoryLocator bfl = (BeanFactoryLocator) instances.get(resourceLocation); 
if (bfl == null ) {
        // 仅仅只是设置了ContextSingletonBeanFactoryLocator里的resourceLocation属性的值,并没有初始化工厂。 
         bfl = new ContextSingletonBeanFactoryLocator(resourceLocation); 
        instances.put(resourceLocation, bfl); 
}

而我们使用工厂模式的时候,一般是把对象初始化好了,再给外部使用,为什么Spring这里要多此一举,在调用getInstance这后还要去调用useBeanFactory来初始化父环境?为什么Spring开发者不写成如下:

?
1
2
3
4
5
6
7
BeanFactoryLocator bfl = (BeanFactoryLocator) instances.get(resourceLocation); 
if (bfl == null ) { 
         bfl = new ContextSingletonBeanFactoryLocator(resourceLocation);
         // 下面这句可能换成 initBeanFactory 类似语句,这里只是打个比方
          bfl.useBeanFactory(parentContextKey); 
         instances.put(resourceLocation, bfl); 
}

本来我认为这个写法是必须的,后来想想也不是,不过这里体现了Spring的灵活设计。如果按排上面的方法进行改造有几点不妥,1,每次都会初始化,开销比较大,可能有需求是需要延迟初始化的。2,每次都需要初始化都需要传入两个参数,分别为:配置文件名与父工厂名,3,类职责混乱,比如一个配置文件中可能定义了多个父环境的Bean,采用Spring这种方法是很清晰的:

?
1
2
3
4
// 返回BeanFactoryLocator方便定位某个配置文件。
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(“classpath*: parentBeanFactory.xml”); 
parentContextRef1 = locator.useBeanFactory( "parent1Key" );
parentContextRef2 = locator.useBeanFactory( "parent2Key" );

而使用我们改造的方法,则要写成如下:

?
1
2
parentContextRef1 = ContextSingletonBeanFactoryLocator.getInstance( "parentBeanFactory.xml" , "parent1Key" ); 
parentContextRef2 = ContextSingletonBeanFactoryLocator.getInstance( "parentBeanFactory.xml" , "parent2Key" );

相当麻烦且无语,引用了也只是这个配置文件中的某一个Bean的引用,没什么意义。

这就是为什么BeanFactoryLocator接口存在的一个原因,用于查找某个配置文件中的一个BeanFactory。

?
1
2
3
public interface BeanFactoryLocator {
         BeanFactoryReference useBeanFactory(String factoryKey) throws BeansException;
}

this.context = createWebApplicationContext(servletContext, parent);

我们来看看这个函数做了些什么:

?
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
protected WebApplicationContext createWebApplicationContext( 
             ServletContext servletContext, ApplicationContext parent) throws BeansException {
        // 获得需要实例化的CONTEXT类名,确定ContextClass的类型。如果在web.xml中配置了contextClass这个parameter,
         // 使用这个指定的类作为ContextClass,会抛出ClassNotFound的异常。反之,使用默认的XmlWebApplicationContext
         Class contextClass = determineContextClass(servletContext); 
  
         // 所有的WebApplicationContext必须实现ConfigurableWebApplicationContext接口 
         if (!ConfigurableWebApplicationContext. class .isAssignableFrom(contextClass)) { 
             throw new ApplicationContextException( "Custom context class [" + contextClass.getName() + 
                     "] is not of type [" + ConfigurableWebApplicationContext. class .getName() + "]" ); 
         }
  
         ConfigurableWebApplicationContext wac = 
                 (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); 
  
        // 设置父环境 
         wac.setParent(parent); 
        // 设置Servlet上下文环境 
         wac.setServletContext(servletContext); 
        // 设置Spring配置文件路径 
         wac.setConfigLocation(servletContext.getInitParameter(CONFIG_LOCATION_PARAM)); 
        customizeContext(servletContext, wac); 
        wac.refresh();
  
        return wac; 
}
  
protected Class determineContextClass(ServletContext servletContext) throws ApplicationContextException {
        // 获得需要实例化的CONTEXT类名,在web.xml中有设置,如果没有设置,那么为空 
         String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); 
        if (contextClassName != null ) { 
            try
                 return ClassUtils.forName(contextClassName); 
             } catch (ClassNotFoundException ex) { 
                 throw new ApplicationContextException( "Failed to load custom context class." , ex); 
            
        } else {
  
            //如果在spring web.xml中没有设置context类位置,那么取得默认context
            //取得defaultStrategies配置文件中的WebApplicationContext属性  
             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." , ex); 
           
       
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties" ;
  
static
      // Load default strategy implementations from properties file. 
      // This is currently strictly internal and not meant to be customized 
      // by application developers. 
      try
          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()); 
     
}

// 在ContextLoader.properties里定义如下

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

再来看看Spring是如果进行初始化ApplicationContext的。就以XmlWebApplicationContext来说,它继承了ConfigurableWebApplicationContext这个接口,里面有个refresh()方法,我们可以看看它的实现(AbstractApplicationContext):

?
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
50
public void refresh() throws BeansException, IllegalStateException { 
         synchronized ( this .startupShutdownMonitor) { 
             // Prepare this context for refreshing. 
             prepareRefresh();
  
             // Tell the subclass to refresh the internal bean factory. 
             ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  
             // Prepare the bean factory for use in this context. 
             prepareBeanFactory(beanFactory);
  
             try
                 // Allows post-processing of the bean factory in context subclasses. 
                 postProcessBeanFactory(beanFactory);
  
                 // Invoke factory processors registered as beans in the context. 
                 invokeBeanFactoryPostProcessors(beanFactory);
  
                 // Register bean processors that intercept bean creation. 
                 registerBeanPostProcessors(beanFactory);
  
                 // Initialize message source for this context. 
                 initMessageSource();
  
                 // Initialize event multicaster for this context. 
                 initApplicationEventMulticaster();
  
                 // Initialize other special beans in specific context subclasses. 
                 onRefresh();
  
                 // Check for listener beans and register them. 
                 registerListeners();
  
                 // Instantiate all remaining (non-lazy-init) singletons. 
                 finishBeanFactoryInitialization(beanFactory);
  
                 // Last step: publish corresponding event. 
                 finishRefresh(); 
             catch (BeansException ex) { 
                 // Destroy already created singletons to avoid dangling resources. 
                 beanFactory.destroySingletons();
  
                 // Reset 'active' flag. 
                 cancelRefresh(ex);
  
                 // Propagate exception to caller. 
                 throw ex; 
            
        
}

这个方法的实现由于涉及的东西比较多,比较国际化,事件等等,等我们理解了后续的源代码分析之后再重新过来进行研究。这样效率更高点。

这样关于Spring在web项目中加载及初始化的方式我们大概也了解的比较清楚了,我们可以看到,Spring就第一步,加载都已经做了很多工作,不得不佩服Spring团队的智慧。

 

最后,Spring加载完成之前,会将ApplicationContext放入ServletContext中,方便程序进行访问。

?
1
2
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this .context); 
currentContextPerThread.put(Thread.currentThread().getContextClassLoader(), this .context);

其中WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE定义如下:

String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

 

Spring环境

加载组件:ContextLoaderListener

配置路径:Servlet环境初始化参数contextConfigLocation指定的路径

缺省路径: 没有缺省路径

 

Spring环境的父环境

加载组件:ContextLoaderListener和ContextSingletonBeanFactoryLocator

配置路径:Servlet环境初始化参数locatorFactorySelector指定Bean工厂定位器使用的给BeanFactory,Servlet环境初始化参数parentContextKey指定Bean工厂定位器用于查找BeanFactory的关键字

缺省路径: parentContextKey的缺省路径是classpath*:beanRefFactory.xml

 

这里我们还有一个功能相近的类没有进行说明:

ContextJndiBeanFactoryLocator

有兴趣的同学可以自己看一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值