Spring IoC实现

在之前已经介绍过Spring IoC对资源的抽象了,也就是Resource接口。当我们加载了指定的资源后,接下来需要做的就是将资源(也就是XML文件)解析成Document实例,并解析成BeanDefinition然后注册。这个过程的整体流程如下:

  1. loadBeanDefinitions(Resource resource)作为方法入口。
  2. doLoadBeanDefinitions(InputSource inputSource, Resource resource)方法实现了真正的加载逻辑,它首先根据XML获取Document实例,然后根据Document实例注册Bean信息。
    2.1. doLoadDocument()方法根据XML获取Document实例,其中会牵扯到获取验证模式和错误处理等过程。
    2.2. registerBeanDefinitions()遍历XML的每一个节点并注册它们的BeanDefinition

接下来就上面获取Document实例和注册BeanDefinition进行分析。

获取Document

解析XML有两种方式,一种是DOM解析,另一种则是SAX解析,关于这两个解析方式在网上也有比较充足的资料,就不在这赘述了。在这一个过程还有两个方面是需要关心的,一个是验证模式,另一个是错误处理(还有一个是Spring实现的EntityResolver,自定义了验证文件的获取方式,在本地建立了一个映射而不需要从网络中获取)。

首先,验证模式同样也有两种,一种是DTD验证模式,另一种是XSD验证模式。因此,在这里需要根据XML文件中的一些信息来探测决定具体使用哪种验证模式,比如说如果内容中包含DOCTYPE则肯定为DTD验证模式,而如果探测不出的话,最终会使用XSD验证模式。错误处理就是当加载Document发生错误时需要做出的反应,简单的实现就是输出错误日志。当以上两个都准备好了后,就先通过DocumentBuilderFactory创建一个DocumentBuilder,再调用DocumentBuilderparse()方法直接解析并返回一个Document实例即可。

注册BeanDefinition

注册BeanDefinition实际上就是通过上面获取到的Document的根节点开始逐个遍历子节点(要先判断根节点是否使用的默认命名空间),然后根据<import/><alias/><bean/><beans/>这四种标签分别进行解析,其中<beans/>标签的处理是一个递归的过程,而<bean/>标签的处理则是需要重点关注的,在这个过程中主要分为两步:解析出BeanDefinition并且完成注册。

Simple IoC实现

获取Document

目前仅支持XSD格式的验证,并且没有实现EntityResolver,也就是说无法根据自定义的策略从本地拿到验证文件,还是默认的从网络中获取。

注册BeanDefinition

在目前的实现中,并没有像Spring IoC一样支持四种标签的解析,这里只解析了<bean/>标签。之后,需要重点关注的一个方法就是parseBeanDefinitionElement(),这是解析<bean/>标签的核心逻辑,主要完成了从<bean/>标签的id属性和name属性还有一些别的属性以及子元素中获取到值并组装成一个BeanDefinition(实际上还会包装一层BeanDefinitionHolder,其中保存了beanNamealiases),下面对这一部分的细节进行分析。

parseBeanDefinitionElement

这个方法主要完成以下这些事:

  1. 解析出<bean>标签中的id属性和name属性,在Spring中是以,作为分隔符取得多个别名,但在这里的实现中只考虑一种别名的情况,也就是没有使用,进行分割。
  2. 优先使用id作为beanName,但如果并没有设置id属性的话,就使用第一个别名作为beanName,那么在这里就只有唯一的一个别名,当id没设置时它就是beanName
  3. 检查beanName和别名的唯一性,如果不唯一,则抛出异常,唯一的话则加入到集合中去。(注意,别名也是需要唯一的)
  4. 解析别的属性以及子元素,开始构造AbstractBeanDefinition。上面几步只是对名称做一些解析,这一步的工作量则相对要大很多,下面是一些比较常见的属性:
    4.1. 解析class属性,最重要的了
    4.2. 解析parent属性,未实现
    4.3. 解析scope属性
    4.4. 解析autowire属性
    4.5. 解析init-method属性
    4.6. 解析destroy-method属性
    4.7. 解析factory-bean属性
    4.8. 解析factory-method属性
    4.9. 解析<lookup-method/>子元素,未实现
    4.10. 解析<replaced-method/>子元素,未实现
    4.11. 解析<property/>子元素,这一步也很重要,它将属性名和属性值封装到了PropertyValue中,并且用PropertyValues封装所有属性(也就是每个BeanDefinition都有一个PropertyValues类型的成员变量)。
    4.12. 解析<constructor-arg/>子元素,未实现
  5. 此时已经构造出了一个相对完整的BeanDefinition了,这时候将其和beanName与别名一起封装成BeanDefinitionHolder对象。
  6. 注册beanName -> BeanDefinition以及alias -> beanName的映射关系(个人认为,这正是IoC的本质所在,通过一个映射表维护一个名称到BeanDefinition的关系,而这个BeanDefinition中封装了这个Bean的各种信息)。这里要注意的是,在注册alias -> beanName的映射关系时,Spring考虑了别名循环指向的问题,它是通过递归来进行判断的。
  7. 此时,这个BeanDefinition已经注册成功,可以等待使用了

快速开始

针对根据XML获取Document实例并注册BeanDefinition的过程做一些简单的测试:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testLoadBeanDefinitions() {
ResourceLoader resourceLoader = new FileSystemResourceLoader();
Resource resource = resourceLoader.getResource("C:\\Users\\canjie\\Desktop\\simple.xml");
BeanDefinitionRegistry registry = new DefaultListableBeanFactory();
BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(registry);
assertEquals(beanDefinitionReader.loadBeanDefinitions(resource), 2);
assertEquals(((DefaultListableBeanFactory) registry).getBeanDefinition("first").getBeanClassName(),
"beans.First");
assertEquals(((DefaultListableBeanFactory) registry).getBeanDefinition("second").getBeanClassName(),
"beans.Second");
}