2.3 IoC容器的初始化过程

简单来说,IoC容器的初始化是由前面介绍的refresh()方法来启动的,这个启动包括BeanDefinition的Resource定位、载入和注册。

(1) Resource定位过程:

指的是BeanDefinition的资源定位,由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。比如文件系统中的Bean定义信息可以使用FileSystemResource进行抽象;类路径中的Bean定义信息可以使用ClassPathResource来使用等。这个定位过程就类似容器寻找数据的过程,就好像水桶装水要先找水。

(2) BeanDefinition的载入:

把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。

(3) 向IoC容器注册这些BeanDefinition的过程:

调用BeanDefinitionRegistry接口的实现来完成,把载入过程中解析得到的BeanDefinition向IoC容器进行注册,在IoC容器内部将BeanDefinition注入到一个HashMap中,IoC容器通过这个HashMap来持有这些BeanDefinition数据。

PS:IoC容器初始化过程不包含Bean依赖注入的实现。在Spring中,Bean定义的载入和依赖注入是两个独立过程,依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。(例外:如果对某个Bean设置了lazyinit属性,那么这个Bean的依赖注入在IoC容器初始化时就预先完成了)。

2.3.1  BeanDefinition的Resource定位

当你使用更为底层的DefaultListableBeanFactory时,首先要定义一个Resource来定位容器使用的BeanDefinition,这个Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。相比之下,在更上层的ApplicationContext中,Spring提供了一系列加载不同Resource的读取器的实现,如FileSystemXmlApplicationContext就提供了从文件系统载入Resource的实现、ClassPathXmlApplication提供了从Class Path载入Resource的实现,XmlWebApplicationContext提供了从web容器中载入

Resource的实现。而DefaultListableBeanFactory只是一个纯粹的IoC容器,需要为它配置特定的读取器。

在FileSystemXmlApplicationContext中,构造函数实现了对configuration进行处理的功能,让所有配置在文件系统中的以XML文件方式存在的BeanDefinition都能够得到有效处理。构造函数中通过refresh()方法来启动IoC容器的初始化。

在初始化FileSystemXmlApplicationContext的过程中,通过IoC容器的初始化的refresh来启动整个调用,使用的IoC容器是DefaultListableBeanFactory,具体的资源载入在XmlBeanDefinitionReader读入BeanDefinition时完成。而getResourceByPath会被子类FileSystemXmlApplicationContext实现,这个方法返回的是一个FileSystemSource对象,通过这个对象,Spring可以进行相关的I/O操作,完成BeanDefinition的定位。

如果是其他的ApplicationContext,则会产生其他种类的Resource,比如ClassPathResource、ServletContextResource等。

完成资源定位之后,具体的数据还没有导入,数据导入将会在载入和解析中完成,就好比用水桶打水,现在已经找到水源了,但是还没有把水装到桶里。

2.3.2  BeanDefinition的载入和解析

这个载入过程相当于把定义的BeanDefinition在IoC容器中转化成一个Spring内部表示的数据结构的过程。IoC容器对Bean的管理和依赖注入这些功能的实现是通过对其持有的BeanDefinition进行各种相关操作完成的。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。

在初始化FileSystemXmlApplicationContext的过程中,通过调用refresh来启动整个BeanDefinition的载入过程,这个初始化通过定义的XmlBeanDefinitionReader来完成。实际使用的IoC容器是DefaultListableBeanFactory,具体的Resource载入在XmlBeanDefinitionReader读入BeanDefinition时实现。由于Spring可以对应不同形式的BeanDefinition,以XML方式定义就使用XmlBeanDefinitionReader,如果是其他BeanDefinition方式,就需要使用其他种类的BeanDefinitionReader来完成数据载入。

载入是在reader.loadBeanDefinition中开始进行的,这个方法是一个接口方法,具体的实现在XmlBeanDefinitionReader中。在读取器中,需要得到代表XML文件的Resource,因为这个Resource对象封装了对XML文件的I/O操作,所以读取器可以在打开I/O流后得到XML的文件对象,有了这个对象,再通过BeanDefinitionParserDelegate对这个XML的文档树进行解析。

解析并转化为容器内部数据结构是在registerBeanDefinition(doc, resource)中完成的。BeanDefinition的载入分成两部分:

1. 首先通过调用XML的解析器得到document对象;

2. 在完成通用的XML解析以后,按照Spring的Bean规则进行解析。

按照Spring的Bean规则进行解析的过程是在documentReader中实现的,处理的结果由BeanDefinitionHolder对象持有,BeanDefinitionHolder除了持有BeanDefinition以外,还持有比如Bean的名字、别名集合等(BeanDefinitionHolder是BeanDefinition对象的封装类,封装了BeanDefinition,Bean的名字和别名)。

BeanDefinitionHolder的生成是通过BeanDefinitionParserDelegate对XML元素进行解析得到的,BeanDefinitionParserDelegate这个类中包含了对各种Spring Bean定义规则的处理,比如对Bean元素怎么处理,即处理XML文件中出现的<bean></bean>这个最常见的元素信息,在这里会把id、name、aliase等属性元素的值读取出来,设置到生成的BeanDefinitionHolder中去。而对各种Bean的属性配置等比较复杂的解析就通过parseBeanDefinitionElement来完成,并把解析结果设置到BeanDefinitionHolder中。在BeanDefinitionParserDelegate类中,一层一层地对BeanDefinition中的定义进行解析,比如从属性元素集合到具体的每一个属性元素,再对具体的属性值的处理,把解析结果封装成PropertyValue对象并设置到BeanDefinition对象中去。

总结:经过这样的逐层解析,XML中定义的BeanDefinition就被整个载入到了IoC容器中,并在容器中建立了数据映射。在IoC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IoC容器中的抽象,这些数据结构可以以AbstractBeanDefinition为入口,让IoC容器执行索引、查询和操作。经过以上的载入过程,IoC容器大致完成了管理Bean对象的数据准备工作(初始化),但依赖注入在这个时候还没有发生,现在,在IoC容器BeanDefinition中存在的只是一些静态的配置信息。

上面所说的过程比较复杂,可以结合以下这个流程图,简单串一下主要的方法。从整体范围来看,载入过程的代码是一步步细化的过程,从web.xml读取的可能是多个路径,一个路径下面可能有多个文件(Resource),一个文件里面有多个Bean,一个Bean里面有多个Property,一个Property里面只能有一个Ref、Value和List。

2.3.2  BeanDefinition在IoC容器中的注册

经过载入和解析的过程,用户定义的BeanDefinition信息已经在容器内建立了数据结构,但是这些数据还不能被供容器直接使用,需要在IoC容器中对这些BeanDefinition数据进行注册。那么注册是在哪里发起的?以及注册的实现类?

在DefaultBeanDefinitionDocumentReader类的processBeanDefinition方法中,其中一个是去完成BeanDefinition的载入,另一个是完成注册。在DefaultListableBeanFactory中实现了BeanDefinitionRegistry的接口,这个接口的实现完成了注册,把解析好的BeanDefinition设置到hashMap中。

完成了注册之后就完成了IoC容器的初始化过程。

猜你喜欢

转载自my.oschina.net/u/3342874/blog/1819519
2.3