通过阅读相关章节内容,Spring中IOC容器的加载中,我们需要了解下列几个概念:
- Resource:是一个定位、访问资源的抽象接口,包含了多种资源操作的基础方法定义,如
getInputStream()
、exists()
、isOpen()
、getDescription()
等。- BeanDefinition:POJO对象在IOC容器中的抽象,通过此数据结构,使IOC容器能方便地对POJO对象进行管理,其中可以设置Bean的一些属性,如:
scope
、beanClassName
、lazyInit
、dependentsOn
等。- BeanFactory:基础容器定义接口,Spring IOC 容器的一个最基础的行为定义的接口,定义了一个IOC容器所需要的最基础的行为规范,包括:
getBean()
等。- ApplicationContext:高级容器定义接口,在BeanFactory的基础上,添加了一些扩展功能,如,
ResoruceLoader
、MessageSource
、ApplicationEventPublisher
等。
ClassPathResource res = new ClassPathResource("bean.xml");
DefaultListableBeanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader();
reader.loadBeanDefinition(res);
从上面的代码清单可看出,编程方式使用一个IOC容器的步骤主要如下:
- 创建IOC配置文件的抽象资源,包含对BeanDefinition的定义
- 创建BeanFactory
- 创建一个载入BeanDefinition的读取器,通过一个回调配置给BeanFactory
- 从定义好的资源位置读取配置信息,具体解析过程由DefinitionReader来完成。
完成这些步骤后,就可以直接使用IOC容器了。
应用上下文的主要功能都在Abstract__ApplicationContext
基类中实现了,而一个具体的应用上下文只需要实现和它自身设计相关的两个功能:
1、启动IOC容器的refresh()
(通常在构造函数中)过程。
2、从文件系统中加载Bean的定义资源,使用getResourceByPath()
得到资源定位,如FileSystemXmlApplicationContext
中使用getResourceByPath
获取一个FileSystemResource
。
由refresh()
启动,整个过程可以拆分为三个部分:
- BeanDefinition的Resource定位
- BeanDefinition的Resource载入
- BeanDefinition的Resource注册
下述过程以FileSystemApplicationContext
为例,分析IOC容器的初始化过程。
由ResourceLoader
通过统一的Resource
接口完成,对各种形式的BeanDefinition提供了同一接口。此过程类似于容器寻找数据的过程。
资源定位的整个过程可总结为:
- FileSystemXmlApplicationContext构造函数调用
AbstractApplicationContext
中的refresh()
方法;- refresh中调用
AbstractApplicationContext
中的obtainFreshBeanFactory()
方法;- obtainFreshBeanFactory中调用
AbstractRefreshableApplicationContext
中的refreshBeanFactory()
方法;- 在
refreshBeanFactory()
中包含下述几步操作:
- 判断若已经建立了BeanFactory,则销毁并关闭它;
- 通过
createBeanFactory()
创建一个DefaultListableBeanFactory
,调用AbstractRefreshableApplicationContext
中的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
,此为一抽象方法,用于将BeanDefinition装入BeanFactory中,其具体实现委托给一个或多个bean definition readerAbstractBeanDefinitionReader
中的一种重载方法loadBeanDefinitions(String location, Set
,其通过判断ResourceLoader类型,使用不同的getResource()方法获取ResourceactualResources) - 以
DefaultResourceLoader
中的getResource()
为例,其中根据传入的loacation字符串的格式进行分别处理:1、若以”/”开头,作为相对路径处理2、若以”classpath:”开头,作为类路径处理3、若为URL资源路径,作为URL资源路径处理4、若都不是,则委托给子类的getResourceByPath(location)
方法实现。其中上述所有处理都是对path进行解析,返回一个Resource对象;
定位完成后为BeanDefinition的载入创造了I/O操作的条件,但是数据还没有开始读入。
把用户定义好的Bean表示成IOC容器内部的数据结构,即BeanDefinition
。下面,以DefaultListableBeanFactory
为例,分析IOC容器完成BeanDefinition载入的过程。
BeanDefinition载入的过程可分为两个部分:通过XML解析器得到document对象
和按照Spring的bean规则解析document对象
refresh()
方法详细地描述了整个ApplicationContext的初始化过程,如BeanFactory的更新
、MessageSource和PostProcessor的注册
等。
createBeanFactory()
方法中调用的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
方法启动对BeanDefinition的载入,它是一个抽象方法,用于将BeanDefinition装入BeanFactory中,其具体实现委托给一个或多个bean definition reader实际载入过程在
AbstractXmlApplicationContext
中实现:上面过程调用的
loadBeanDefinitions(beanDefinitionReader)
具体实现内容如下:
- 获取BeanDefinition信息的Resource定位
- 调用
XmlBeanDefinitionReader
读取,具体载入过程委托给BeanDefinitionReader
完成- 使用
XmlBeanDefinitionReader
载入BeanDefinition到容器中
reader.loadBeanDefinitions
中开始进行BeanDefinition的载入,此时,其XmlBeanDefinitionReader
父类AbstractbeanDefinitionReader
已经为BeanDefinition的载入做好了准备:
- 上面的
`loadBeanDefinitions(Resource resource)
是一个接口方法,具体实现在XmlBeanDefinitionReader
中:
ps:
上述过程使用的是XML方式定义的BeanDefinition,故使用XmlBeanDefinitionReader
,若使用其他方式的BeanDefinition,则需要使用对应种类的BeanDefinitionReader
来完成载入工作
Spring的BeanDefinition按照Spring的Bean语义要求进行解析并转化为容器内部数据结构的过程(同时包含对载入的Bean数量进行统计)由registerBeanDefinitions(doc, resource)
完成。
- 使用
documentReader
按照Spring的Bean规则document对象,此处使用默认设置的DefaultBeanDefinitionDocumentReader
进行解析
![]()
ps:下述过程由本人参照4.3.11源码进行总结,与原书中有差异,感兴趣的读者可自行获取4.3.11源码进行验证。
registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
中调用doRegisterBeanDefinitions(Element root)
根据root下的element注册bean
![]()
- 调用
parseBeanDefinitions(root, this.delegate)
解析document中root级别目录要素:"import"
、"alias"
、"bean"
。
![]()
ps:此处我们着重看默认元素中元素的解析过程。其他元素解析感兴趣的同学可以自行查阅spring4.3.11源码中
DefaultBeanDefinitionDocumentReader
内容
BeanDefinitionParserDelegate
中的parseCustomElement(Element ele)
解析自定义元素,此处就不做详细跟踪研究了,感兴趣的看官可以自行去源码中研究DefaultBeanDefinitionDocumentReader
中的parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)
解析默认元素
![]()
processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)
完成BeanDefinition的处理,并将处理结果交由BeanDefinitionHolder
对象持有,BeanDefinitionHolder
对象还持有其他与BeanDefinition
对象使用相关的信息,如:Bean的名字
、别名集合
等
![]()
BeanDefinitionParserDelegate
中的parseBeanDefinitionElement(Element ele, BeanDefinition containingBean)
完成具体的Spring BeanDefinition解析
![]()
parseBeanDefinitionElement(
对BeanDefinition中定义的元素进行详细处理,如:
Element ele, String beanName, BeanDefinition containingBean)attribute值处理
ps:这里我们跟踪一下bean中property属性的解析处理,从而了解解析元素的具体操作parsePropertyElements(Element beanEle, BeanDefinition bd)
获取bean下property元素并启动解析过程
![]()
parsePropertyElement(Element ele, BeanDefinition bd)
开启value(结果由PropertyValue持有)和meta的详细解析,并将解析结果转载到BeanDefinition中
![]()
parsePropertyValue(Element ele, BeanDefinition bd, String propertyName)
解析property元素的值:value、ref、子元素
![]()
parsePropertySubElement(Element ele, BeanDefinition bd, String defaultValueType)
解析property下的子元素
![]()
![]()
- 这里以解析list为例,看看对于property下的子元素的解析过程
![]()
其中具体的List中元素的解析如下
至此,xml文件中定义的BeanDefinition就被整个载入到IOC容器中,并在容器中建立了数据映射,即将POJO抽象到了IOC容器中。从而,可以以AbstractBeanDefinition为入口,让IOC容器执行索引
、查询
、操作
。以上工作使IOC容器大致完成了管理Bean对象的数据准备工作(初始化过程)。由于依赖注入
在此时还没有发生,在IOC的BeanDefinition中存在的还只是一些静态配置信息,若要完全发挥容器作用,需要完成下述数据向容器注册的过程。
通过调用DefaultListableBeanFactory
中实现BeanDefinitionRegistry
接口的registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
方法完成。此过程把载入过程中解析得到的BeanDefinition向IOC容器进行注册。
__ps:__IOC容器内部将BeanDefinition注入到DefaultListableBeanFactory
中创建的一个hashMap中:
/** Map of singleton and non-singleton bean names, keyed by dependency type */
private final Map, String[]> allBeanNamesByType = new ConcurrentHashMap, String[]>(64);
其中注册的具体实现如下:
+ 完整的代码清单我这里就不截取了,只放几个关键点的代码截图
至此,IOC容器初始化的过程就全部完成了,此时,在使用的IOC容器DefinitionBeanFactory中已经建立了整个Bean的配置信息,而且这些BeanDefinition已经可以被容器使用,他们都在beanDefinitionmap里被检索使用。容器的作用就是对这些信息进行处理和维护。这些信息是容器建立依赖反转的基础。
注意:此处所说的IOC容器初始化过程中不包括Bean依赖注入的实现。在Spring IOC的设计中,Bean的定义载入和依赖注入是两个独立的过程。依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候。但是若配置了lazyinit属性,Bean的依赖注入在IOC容器初始化时就完成了。