3.3 Spring IoC容器的初始化
IoC容器的初始化过程是通过refresh()方法启动的,refresh()方法的调用标志着IoC容器正式启动。IoC容器的初始化包括Resource资源定位、BeanDefinition载入和解析、BeanDefinition注册三个基本过程。Spring把这三个过程分开,并使用不同的模块来完成,从而方便自己定义IoC容器的初始化过程。
Resource资源定位:具体指BeanDefinition的资源定位,这个过程就是容器找数据的过程,就像用水桶装水需要先找到水一样。
BeanDefinition载入和解析:把用户定义好的Bean表示成IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefition。
BeanDefinition注册:BeanDefinition注册是通过BeanDefinitionRegistry接口的实现来完成的。把载入过程中解析得到的BeanDefinition向IoC容器进行注册,在IoC容器内部将BeanDefinition注入一个HashMap中,IoC容器就是通过这个HashMap来持有这些Bean数据的。
通过下面一段代码来了解IoC初始化的开端:
ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource);
“ClassPathResource resource=new ClassPathResource("bean.xml");”表示根据XML配置文件创建Resource资源对象。ClassPathResource是Resource接口的子类,bean.xml文件中的内容是我们定义的Bean信息。
“DefaultListableBeanFactory factory=new DefaultListableBeanFactory();”表示创建一个BeanFactory。DefaultListableBeanFactory是BeanFactory的一个子类,BeanFactory作为一个接口,其实它本身是不能够独立使用的,而DefaultListableBeanFactory则是真正可以独立使用的IoC容器,它是整个Spring IoC的始祖。
“XmlBeanDefinitionReader reader=new XmlBeanDefinitionReader(factory);”表示创建XmlBeanDefinition Reader读取器,用于载入BeanDefinition。
“reader.loadBeanDefinitions(resource);”表示开启Bean的载入和注册进程,完成后的Bean放置在IoC容器中。
1. Resource资源定位
Resource资源的定位需要Resource和ResourceLoader两个接口互相配合,代码“NewClassPathResource("bean.xml")”定义了资源,ResourceLoader则使用XmlBeanDefinitionReader构造方法进行初始化,代码如下:
直接调用父类AbstractBeanDefinitionReader的代码如下:
核心在于是否设置ResourceLoader,如果已设置ResourceLoader则用设置好的,否则使用PathMatching ResourcePatternResolver,该类是一个集大成的ResourceLoader。
2. BeanDefinition 载入和解析
reader.loadBeanDefinitions(resource)开启BeanDefinition的解析过程,代码如下:
这个方法会将资源Resource包装成一个EncodedResource实例对象,然后调用loadBeanDefinitions()方法,而将Resource封装成EncodedResource主要是为了对Resource进行编码,保证内容读取的正确性。代码如下:
从encodedResource源中获取XML的解析源,调用doLoadBeanDefinitions()执行具体的解析过程,代码如下:
在该方法中主要做两件事:
(1)根据XML解析源获取相应的Document对象;
(2)调用registerBeanDefinitions()开启BeanDefinition的解析注册过程。
调用doLoadDocument()会将Bean定义的资源转换为Document对象,代码如下:
loadDocument()方法接受以下5个参数。
(1)inputSource:加载Document的Resource源。
(2)entityResolver:解析文件的解析器。
(3)errorHandler:处理加载Document对象的过程的错误。
(4)validationMode:验证模式。
(5)namespaceAware:命名空间支持。如果要提供对XML名称空间的支持,则为True。
loadDocument()在类DefaultDocumentLoader中提供了实现方法,代码如下:
接下来将其解析为Spring IoC管理的Bean对象,并将其注册到容器中。这个过程通过方法registerBean Definitions()实现,代码如下:
首先创建BeanDefinition的解析器BeanDefinitionDocumentReader,然后调用documentReader.register BeanDefinitions()开启解析过程。这里使用的是委派模式,具体的实现由子类DefaultBeanDefinitionDocument Reader完成,代码如下:
对Document对象的解析,从Document对象中获取根元素Root,然后调用doRegisterBeanDefinitions()开启真正的解析过程,代码如下:
preProcessXml()、postProcessXml()为前置、后置增强处理,目前Spring中都是空实现,parseBean Definitions()是对根元素Root的解析注册过程,代码如下:
迭代Root元素的所有子节点,对其进行判断,若节点为默认命名空间,则ID调用parseDefaultElement()开启默认标签的解析注册过程,否则调用parseCustomElement()开启自定义标签的解析注册过程。
若定义的元素节点使用的是Spring默认命名空间,则调用parseDefaultElement()进行默认标签解析,代码如下:
对于默认标签,则由parseCustomElement()负责解析,代码如下:
获取节点的namespaceUri,然后根据该namespaceUri获取相对应的Handler,调用Handler的parse()方法即完成自定义标签的解析和注入。
3. BeanDefinition 注册
经过上面的解析,已将Document对象里面的Bean标签解析成了一个个BeanDefinition,下一步则是将这些BeanDefinition注册到IoC容器中。动作的触发是在解析Bean标签完成后,代码如下:
调用BeanDefinitionReaderUtils.registerBeanDefinition()注册,其实也是调用BeanDefinitionRegistry的registerBeanDefinition()来注册BeanDefinition,不过最终是在DefaultListableBeanFactory中实现的,代码如下:
这段代码最核心的部分是“this.beanDefinitionMap.put(beanName, beanDefinition)”,所以注册过程是利用一个Map的集合对象来存放,Key是beanName,Value是BeanDefinition。
至此,整个IoC的初始化过程就完成了,包括Bean资源的定位,转换为Document对象并对其进行解析,以及注册到IoC容器中。现在,IoC容器中已经建立了整个Bean的配置信息,这些Bean可以被检索、使用、维护,它们是控制反转的基础,是后面注入Bean的依赖。