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

2019年12月05日 阅读数:149
这篇文章主要向大家介绍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</