1、2.3.1 BeanDefinition 的 Resource 定位以编程的方式使用 DefaultListableBeanFactory 时,我们可以看到,首先定义一个Resource 来定位容器使用的 BeanDefinition。这时使用的是 ClassPathResource,意味着Spring 会在类路径中寻找以文件形式存在的 BeanDefinition 信息。ClassPathResource res = new ClassPathResource(“beans.xml“);这个定义的 Resource 并不能让 DefaultListableBeanFactory 直接使用,S
2、pring 是通过BeanDefinitionReader 来对这些信息进行处理的。在这里,我们也可以看到使用ApplicationContext 相对于直接使用 DefaultListableBeanFactory 的好处。因为在ApplicationContext 中,Spring 已经为我们提供了一系列加载不同 Resource 的读取器的实现,而 DefaultListableBeanFactory 只是一个纯粹的 IoC 容器,需要为它配置特定的读取器才能完成这些功能。当然,有利就有弊,使用 DefaultListableBeanFactory 这种更底层的容器,却能提高我们定制 I
3、oC 容器的灵活性。回到我们经常使用的 ApplicationContext 上来,例如 FileSystemXmlApplication-Context、ClassPathXmlApplicationContext 以及 XmlWebApplicationContext 等。简单地从这些类的名字上分析,可以清楚地看到它们可以提供哪些不同的 Resource 读入功能,比如FileSystemXmlApplicationContext 可以从文件系统载入 Resource,ClassPathXm-lApplicationContext 可以从 Class Path 载入 Resource,Xm
4、lWebApplicationContext 可以在Web 容器中载入 Resource,等等。下面以 FileSystemXmlApplicationContext 为例,通过分析这个 ApplicationContext 的实现来看看它是怎样完成这个 Resource 定位过程的。作为辅助,我们可以在图 2-4 中看到相应的 ApplicationContext 继承体系。从图 2-4 中可以看到,这个 FileSystemXmlApplicationContext 已经通过继承 Abstra-ctApplicationContext 具备了 ResourceLoader 读入以 Reso
5、urce 定义的 BeanDefiniti-on 的能力,因为 AbstractApplicationContext 的基类是 DefaultResourceLoader。下面看看FileSystemXmlApplicationContext 的具体实现,如代码清单 2-4 所示。代码清单 2-4 FileSystemXmlApplicationContext 的实现1. public class FileSystemXmlApplicationContext extends 2. AbstractXmlApplicationContext 3. public FileSystemXmlApp
6、licationContext() 4. 5. public 6. FileSystemXmlApplicationContext(ApplicationContext parent) 7. super(parent); 8. 9. /这个构造函数的 configLocation 包含的是 BeanDefinition 10. 所在的文件路径。 11. public FileSystemXmlApplicationContext(String 12. configLocation) throws BeansException 13. this(new String configLocation
7、, true, 14. null); 15. 16. /这个构造函数允许 configLocation 包含多个 BeanDefiniti 17. on 的文件路径。 18. public FileSystemXmlApplicationContext(String 19. configLocations) throws BeansException 20. this(configLocations, true, null); 21. 22. /* 23. *这个构造函数在允许 configLocation 包含多个 BeanDefinit 24. ion 的文件路径的同时, 25. *还允许
8、指定自己的双亲 IoC 容器。 26. */ 27. public FileSystemXmlApplicationContext(String 28. configLocations, ApplicationContext 29. parent) throws BeansException 30. this(configLocations, true, parent); 31. 32. public FileSystemXmlApplicationContext(String 33. configLocations, boolean refresh) 34. throws BeansExce
9、ption 35. this(configLocations, refresh, null); 36. 37. /* 38. *在对象的初始化过程中,调用 refresh 函数载入 BeanDefini 39. tion,这个 refresh 40. *启动了 BeanDefinition 的载入过程,我们会在下面进行详 41. 细分析。 42. */ 43. public FileSystemXmlApplicationContext(String 44. configLocations, boolean refresh, 45. ApplicationContext parent) 46.
10、 throws BeansException 47. super(parent); 48. setConfigLocations(configLocations); 49. if (refresh) 50. refresh(); 51. 52. 53. /* 54. *这是应用于文件系统中 Resource 的实现,通过构造一个 Fil 55. eSystemResource 来 56. *得到一个在文件系统中定位的 BeanDefinition。 57. */ 58. /* 59. *这个 getResourceByPath 是在 BeanDefinitionReader 的 load 60
11、. BeanDefintion 中被调用的。 61. *loadBeanDefintion 采用了模板模式,具体的定位实现实际 62. 上是由各个子类完成的。 63. */ 64. protected Resource getResourceByPath(String path) 65. if (path != null 67. 68. return new FileSystemResource(path); 69. 70. 在 FileSystemApplicationContext 中,我们可以看到实现了两个部分的功能,一部分是在构造函数中,对 configuration 进行处理,使得所
12、有在配置在文件系统中的 XML 文件方式的 BeanDefnition 都能够得到有效的处理,比如实现了 getResourceByPath 方法,这个方法是一个模板方法,是为读取 Resource 服务的。对于 IoC 容器功能的实现,这里没有涉及,因为它继承了 AbstractXmlApplicationContext,关于 IoC 容器功能相关的实现,都是在FileSystemXmlApplicationContext 中完成的,但是在构造函数中通过 refresh 来启动了 IoC容器的初始化,这个 refresh 方法非常重要,也是我们以后分析容器初始化过程实现的一个重要入口。注意
13、FileSystemApplicationContext 是一个支持 XML 定义 BeanDefinition 的ApplicationContext,并且可以指定以文件形式的 BeanDefinition 的读入,这些文件可以使用文件路径和 URL 定义来表示。在测试环境和独立应用环境中,这个 ApplicationContext是非常的有用的。根据图 2-5 的调用关系分析,我们可以清楚地看到整个 BeanDefinition 资源定位的过程。这个对 BeanDefinition 资源定位的过程,最初是由 refresh 来触发的,这个 refresh 的调用是在 FileSystemX
14、mlBeanFactory 的构造函数中启动的。大家看了上面的调用过程可能会比较好奇,这个 FileSystemXmlApplicationContext 在什么地方定义了 BeanDefinition 的读入器 BeanDefinitionReader,从而完成 BeanDefi-nition信息的读入呢?在前面分析过,在 IoC 容器的初始化过程中,BeanDefinition 资源的定位、读入和注册过程是分开进行的,这也是解耦的一个体现。关于这个读入器的配置,可以到FileSystemXmlApplicationContext 的基类 AbstractRefreshableApplica
15、tionContext 中看看它是怎样实现的。我们重点看看 AbstractRefreshableApplicationContext 的 refreshBeanFactory 方法的实现,这个 refreshBeanFactory 被 FileSystemXmlApplicationContext 构造函数中的 refresh 调用。在这个方法里,通过 createBeanFactroy 构建了一个 IoC 容器供 Appl-icationContext 使用。这个 IoC 容器就是我们前面提到过的 DefaultListableBeanFactory,同时,它启动了loadBeanDefi
16、nitions 来载入 BeanDefinition,这个过程和我们前面看到的编程式的使用 IoC容器(XmlBeanFactory)的过程非常类似。从代码清单 2-4 中可以看到,在初始化 FileSystmXmlApplicationContext 的过程中,通过 IoC 容器的初始化的 refresh 来启动整个调用,使用的 IoC 容器是DefultListableBeanFactory。具体的资源载入在 XmlBeanDefinitionReader 读入BeanDefinition 时完成,在 XmlBeanDefinitionReader 的基类 AbstractBeanDefi
17、nitionReader中可以看到这个载入过程的具体实现。对载入过程的启动,可以在AbstractRefreshableApplicationCont-ext 的 loadBeanDefinitions 方法中看到,如代码清单 2-5所示。代码清单 2-5 AbstractRefreshableApplicationContext 对容器的初始化1. protected final void refreshBeanFactory() throws BeansException 2. /这里判断,如果已经建立了 BeanFactory,则销毁并关闭该 BeanFactory。 3. if (ha
18、sBeanFactory() 4. destroyBeans(); 5. closeBeanFactory(); 6. 7. /这里是创建并设置持有的 DefaultListableBeanFactor 的地方。 8. /同时调用 loadBeanDefinitions 再载入 BeanDefinitione 的信息。 9. try 10. DefaultListableBeanFactory beanFactory = createBeanFactory(); 11. beanFactory.setSerializationId(getId(); 12. customizeBeanFacto
19、ry(beanFactory); 13. loadBeanDefinitions(beanFactory); 14. synchronized (this.beanFactoryMonitor) 15. this.beanFactory = beanFactory; 16. 17. 18. catch (IOException ex) 19. throw new ApplicationContextException(“I/O error parsing XMLdocument 20. for “ + getDisplayName(), ex); 21. 22. 23. /* 24. *这就是
20、在上下文中创建 DefaultListableBeanFactory 的地方,而getInternalParentBeanFactory()的 *具体实现可以参看 AbstractApplicationContext 中的实现,会根据容器已有的双亲 IoC容器的信息来*生成 DefaultListableBeanFactory 的双亲 IoC 容器。1. */ 2. protected DefaultListableBeanFactory createBeanFactory() 3. return new DefaultListableBeanFactory(getInternalParentBeanFactory();4. 5. /*