宅男Coder,没有其他爱好,闲暇之余抱着瞻仰的心态去阅读一下Spring的源码,期许能收获一支半解。要学习Spring的源码,第一步自然是下载和编译Spring的源码,这个我在之前的博文中已经发表过了。具体可参考:SpringFramework×××和编译教程 

面对茫茫多的Spring的工程和代码,很多人可能会无从下手。其实想想,Spring也是有入口的,那就是配置文件的加载。Spring容器的构建完全是基于配置文件的配置的。不论是Web工程,还是普通的Java应用,加载Spring配置文件都是首要的工作。所以,我就从配置文件的加载学起。
 
要加载配置文件,首先当然是要找到该文件。大多数人通常都是在Web应用中使用Spring。网上搜搜配置,配置文件的名字就叫约定的:applicationContext.xml,然后往编译路径下一扔,Spring自然就好用了,就没过多的关注过其他容器初始化的问题。其实,一个自然应该想到的问题就是:一个普通的J2SE应用该如何使用Spring呢?答案很简单:new 出一个ApplicationContext的实例就好了。例如:
  1. private static final String SPRINT_FILEPATH_CONTEXT = "D:\\workspace-home\\OpenSourceStudy\\src\\main\\resources\\spring\\app-context.xml";            
  2. ApplicationContext  appContext = new FileSystemXmlApplicationContext(  
  3.                 SPRINT_FILEPATH_CONTEXT);  
只需上述一行代码,一个基于指定的配置文件的Spring容器就初始化完成了。
 
下面,我们来仔细看看FileSystemXmlApplicationContext这个类:
  1.   public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { 
  2.  
  3.     /** 
  4.      * Create a new FileSystemXmlApplicationContext for bean-style configuration. 
  5.      * @see #setConfigLocation 
  6.      * @see #setConfigLocations 
  7.      * @see #afterPropertiesSet() 
  8.      */ 
  9.     public FileSystemXmlApplicationContext() { 
  10.     } 
  11.  
  12.     /** 
  13.      * Create a new FileSystemXmlApplicationContext for bean-style configuration. 
  14.      * @param parent the parent context 
  15.      * @see #setConfigLocation 
  16.      * @see #setConfigLocations 
  17.      * @see #afterPropertiesSet() 
  18.      */ 
  19.     public FileSystemXmlApplicationContext(ApplicationContext parent) { 
  20.         super(parent); 
  21.     } 
  22.  
  23.     /** 
  24.      * Create a new FileSystemXmlApplicationContext, loading the definitions 
  25.      * from the given XML file and automatically refreshing the context. 
  26.      * @param configLocation file path 
  27.      * @throws BeansException if context creation failed 
  28.      */ 
  29.     public FileSystemXmlApplicationContext(String configLocation) throws BeansException { 
  30.         this(new String[] {configLocation}, truenull); 
  31.     } 
  32.  
  33.     /** 
  34.      * Create a new FileSystemXmlApplicationContext, loading the definitions 
  35.      * from the given XML files and automatically refreshing the context. 
  36.      * @param configLocations array of file paths 
  37.      * @throws BeansException if context creation failed 
  38.      */ 
  39.     public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { 
  40.         this(configLocations, truenull); 
  41.     } 
  42.  
  43.     /** 
  44.      * Create a new FileSystemXmlApplicationContext with the given parent, 
  45.      * loading the definitions from the given XML files and automatically 
  46.      * refreshing the context. 
  47.      * @param configLocations array of file paths 
  48.      * @param parent the parent context 
  49.      * @throws BeansException if context creation failed 
  50.      */ 
  51.     public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { 
  52.         this(configLocations, true, parent); 
  53.     } 
  54.  
  55.     /** 
  56.      * Create a new FileSystemXmlApplicationContext, loading the definitions 
  57.      * from the given XML files. 
  58.      * @param configLocations array of file paths 
  59.      * @param refresh whether to automatically refresh the context, 
  60.      * loading all bean definitions and creating all singletons. 
  61.      * Alternatively, call refresh manually after further configuring the context. 
  62.      * @throws BeansException if context creation failed 
  63.      * @see #refresh() 
  64.      */ 
  65.     public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { 
  66.         this(configLocations, refresh, null); 
  67.     } 
  68.  
  69.     /** 
  70.      * Create a new FileSystemXmlApplicationContext with the given parent, 
  71.      * loading the definitions from the given XML files. 
  72.      * @param configLocations array of file paths 
  73.      * @param refresh whether to automatically refresh the context, 
  74.      * loading all bean definitions and creating all singletons. 
  75.      * Alternatively, call refresh manually after further configuring the context. 
  76.      * @param parent the parent context 
  77.      * @throws BeansException if context creation failed 
  78.      * @see #refresh() 
  79.      */ 
  80.     public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) 
  81.             throws BeansException { 
  82.  
  83.         super(parent); 
  84.         setConfigLocations(configLocations); 
  85.         if (refresh) { 
  86.             refresh(); 
  87.         } 
  88.     } 
  89.  
  90.  
  91.     /** 
  92.      * Resolve resource paths as file system paths. 
  93.      * <p>Note: Even if a given path starts with a slash, it will get 
  94.      * interpreted as relative to the current VM working directory. 
  95.      * This is consistent with the semantics in a Servlet container. 
  96.      * @param path path to the resource 
  97.      * @return Resource handle 
  98.      * @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath 
  99.      */ 
  100.     @Override 
  101.     protected Resource getResourceByPath(String path) { 
  102.         if (path != null && path.startsWith("/")) { 
  103.             path = path.substring(1); 
  104.         } 
  105.         return new FileSystemResource(path); 
  106.     } 
  107.  
一共七个构造函数和一个复写的方法。我们现在重点关注构造函数,除前两个之外,其他的构造函数都最终指向构造函数:
  1. public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) 
  2.             throws BeansException { 
  3.  
  4.         super(parent); 
  5.         setConfigLocations(configLocations); 
  6.         if (refresh) { 
  7.             refresh(); 
  8.         } 
只是对没有传入的参数,给了一个默认值。
该构造函数有三个参数:
  • String[] configLocations - 配置文件的路径数组。是数组也就是说,支持同时传入多个配置文件路径。
  • boolean refresh - 是否刷新,如果是true,则会开始初始化Spring容器, false则暂时不初始化Spring容器。 
  • ApplicationContext parent - Spring容器上下文。即可以传入已经初始化过的Spring容器,新初始化的容器会包含parent上下文中的内容,例如:父容器中定义的bean等。
 
:refresh()方法可谓Spring的核心的入口函数,Spring容器的初始化正是由此开始。一些Spring学习的书中,也向学习Spring的读者推荐,如果感到无所适从,可从该方法入手,研究Spring的整个生命周期。后续我们也会从该方法入手重点研究。现在只需理解,其为Spring容器初始化的一个“开关”即可。
 
前两个构造函数与该构造函数最大的区别就是,没有调用refresh函数。也就是说,Spring容器,此时并未初始化。此时如果用getBean方法去获取Bean的实例,会报容器并未初始化的异常。
 
下面给出一些关于构造函数的测试用例,能更直观、具体的说明问题:
  1. private String filePath = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\app-context.xml"
  2.     private String parentFilePath = "D:\\workspace-home\\spring-custom\\src\\main\\resources\\spring\\parent-context.xml"
  3.  
  4.     /** 
  5.      * 直接指定一个单一的配置文件初始化Spring容器。容器中包含该指定配置文件中定义的Bean{@code VeryCommonBean}。 
  6.      *  
  7.      * @author lihzh 
  8.      * @date 2012-4-29 下午9:39:27 
  9.      */ 
  10.     @Test 
  11.     public void testFileContextWithPath() { 
  12.         ApplicationContext appContext = new FileSystemXmlApplicationContext( 
  13.                 filePath); 
  14.         assertNotNull("The app context should not be null.", appContext); 
  15.         assertNotNull(appContext.getBean(VeryCommonBean.class)); 
  16.     } 
  17.  
  18.     /** 
  19.      * 将已经初始化好的Spring容器上下文传递给新的Spring容器。需手动调用容器的刷新 
  20.      * {@code ConfigurableApplicationContext#refresh()}(初始化)。 
  21.      *  
  22.      * @author lihzh 
  23.      * @date 2012-5-4 下午9:28:40 
  24.      */ 
  25.     @Test 
  26.     public void testFileContextWithParentOnly() { 
  27.         ApplicationContext parentContext = new FileSystemXmlApplicationContext( 
  28.                 parentFilePath); 
  29.         ConfigurableApplicationContext appContext = new FileSystemXmlApplicationContext( 
  30.                 parentContext); 
  31.         assertNotNull("The parent context should not be null.", parentContext); 
  32.         assertNotNull(appContext); 
  33.         // 需要手动刷新(初始化)容器 
  34.         appContext.refresh(); 
  35.         assertNotNull(parentContext.getBean(ParentBean.class)); 
  36.         assertNotNull(appContext.getBean(ParentBean.class)); 
  37.     } 
  38.  
  39.     /** 
  40.      * 根据指定的配置文件和已经初始化好的Parent容器上下文,实例化一个新的Spring容器,该容器中包含这两部分的信息。 
  41.      *  
  42.      * @author lihzh 
  43.      * @date 2012-5-4 下午10:07:20 
  44.      */ 
  45.     @Test 
  46.     public void testFileContextWithParentAndItself() { 
  47.         ApplicationContext parentContext = new FileSystemXmlApplicationContext( 
  48.                 parentFilePath); 
  49.         ApplicationContext appContext = new FileSystemXmlApplicationContext( 
  50.                 new String[] { filePath }, parentContext); 
  51.         assertNotNull("The parent context should not be null", parentContext); 
  52.         assertNotNull(appContext); 
  53.         assertTrue("Should not have bean:[VeryCommonBean] in parent context."
  54.                 !parentContext.containsBean("VeryCommonBean")); 
  55.         assertNotNull(parentContext.getBean(ParentBean.class)); 
  56.         assertNotNull(appContext.getBean(ParentBean.class)); 
  57.         assertNotNull("Should have bean:[VeryCommonBean]."
  58.                 appContext.getBean("VeryCommonBean")); 
  59.     } 
  60.  
  61.     /** 
  62.      * 同时指定多个配置文件来初始化Spring容器 
  63.      *  
  64.      * @author lihzh 
  65.      * @date 2012-5-4 下午10:09:02 
  66.      */ 
  67.     @Test 
  68.     public void testFileContextWithMultiPath() { 
  69.         ApplicationContext appContext = new FileSystemXmlApplicationContext( 
  70.                 parentFilePath, filePath); 
  71.         assertNotNull(appContext); 
  72.         assertNotNull(appContext.getBean(ParentBean.class)); 
  73.         assertNotNull("Should have bean:[VeryCommonBean]."
  74.                 appContext.getBean(VeryCommonBean.class)); 
  75.     } 
了解这些有什么用?
     1.  学会在一个普通的J2SE应用中使用Spring,初始化Spring容器。
     2.  了解初始化Spring容器的几种方式和传入的参数,可配合不同的场景使用。例如:在程序运行期动态初始化一个新的Spring容器,包含已经初始化的容器,即可用与parent相关的构造函数。再例如:在可能的特定的场景下,Spring容器需要在特定的实际初始化,可先调用空构造函数,再传入location,再手动refresh。或者直接给refresh传入false,再手动refresh。再比如:配合Spring的foo包,可监控Spring配置文件的变化,利用refresh函数,实时更新Spring容器等等。
     3.  其他读者可自行发挥想象~~
下期留待思考的:
    1.  支持路径格式的研究。(绝对?相对?通配符?classpath格式又如何?)
    2.  配合placeholder使用的路径问题研究。
    3.  路径如何解析?

 OneCoder独立博客地址:http://www.coderli.com/spring-filexml-constructor