Spring IoC实现
在之前已经介绍过Spring IoC对资源的抽象了,也就是Resource
接口。当我们加载了指定的资源后,接下来需要做的就是将资源(也就是XML文件)解析成Document
实例,并解析成BeanDefinition
然后注册。这个过程的整体流程如下:
loadBeanDefinitions(Resource resource)
作为方法入口。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
,再调用DocumentBuilder
的parse()
方法直接解析并返回一个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
,其中保存了beanName
和aliases
),下面对这一部分的细节进行分析。
parseBeanDefinitionElement
这个方法主要完成以下这些事:
- 解析出
<bean>
标签中的id
属性和name
属性,在Spring中是以,
作为分隔符取得多个别名,但在这里的实现中只考虑一种别名的情况,也就是没有使用,
进行分割。 - 优先使用id作为
beanName
,但如果并没有设置id
属性的话,就使用第一个别名作为beanName
,那么在这里就只有唯一的一个别名,当id没设置时它就是beanName
。 - 检查
beanName
和别名的唯一性,如果不唯一,则抛出异常,唯一的话则加入到集合中去。(注意,别名也是需要唯一的) - 解析别的属性以及子元素,开始构造
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/>
子元素,未实现 - 此时已经构造出了一个相对完整的
BeanDefinition
了,这时候将其和beanName
与别名一起封装成BeanDefinitionHolder
对象。 - 注册
beanName
->BeanDefinition
以及alias
->beanName
的映射关系(个人认为,这正是IoC的本质所在,通过一个映射表维护一个名称到BeanDefinition
的关系,而这个BeanDefinition
中封装了这个Bean的各种信息)。这里要注意的是,在注册alias
->beanName
的映射关系时,Spring考虑了别名循环指向的问题,它是通过递归来进行判断的。 - 此时,这个
BeanDefinition
已经注册成功,可以等待使用了
快速开始
针对根据XML获取Document
实例并注册BeanDefinition
的过程做一些简单的测试:1
2
3
4
5
6
7
8
9
10
11
12
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");
}