博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从源码看Spring中IOC容器的实现(二):IOC容器的初始化
阅读量:6757 次
发布时间:2019-06-26

本文共 4894 字,大约阅读时间需要 16 分钟。

初始化的三个主要过程

Spring中IOC容器的初始化是由refresh()方法来启动的,该方法标志着IOC容器的正式启动。具体来说,这个启动包括BeanDefinition的Resource定位、载入和注册三个基本过程。

注意:这里的初始化不包含Bean依赖注入的实现

Spring将上述三个过程分离,使用相应的ResourceLoader、BeanDefinitionReader等模块,这样设计可以让用户更加灵活地对这三个过程进行剪裁或扩展,

  1. Resoure定位:即BeanDefinition的资源定位,由ResourceLoader通过统一的Resource接口来完成,Resource对各种形式的BeanDefinition的使用都提供了统一接口。
  2. BeanDefinition的载入:这个载入过程是把用户定义好的Bean表示成IOC容器内部的数据结构,即BeanDefinition。
  3. BeanDefinition的注册:通过调用BeanDefinitionRegistry接口的实现来完成。这个注册过程把载入过程中解析得到的BeanDefinition向IOC容器中注册,实际是注入到一个HashMap中。

BeanDefinition:

上一篇文章我们介绍了Spring提供的基本IOC容器的接口定义和实现.在这些Spring提供的基本IOC容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及他们之间的相互依赖关系。

BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。对IOC容器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理来完成的。

Resource定位

以FileSystemXmlApplicationContext为例。其通过getResourceByPath方法加载xml,返回一个Resource。这里使用了模板方法模式。

在FileResourceLoader中可以找到同样的方法,而该方法来自于FileResourceLoader重写其父类DefaultResourceLoader,该方法在ResourceLoader接口中定义,在FileResourceLoader和DefaultResourceLoader中都提供了各自的实现。而其调用在DefaultResourceLoader的getResource方法中:

这里确实是模板方法模式,子类覆盖父类实现。而通过查找该方法的调用,首先是看FileSystemXmlApplicationContext的loadBeanDefinitions方法,该方法在AbstractXmlApplicationContext中实现:

此时已经在AbstractXmlApplicationContext中注入了对应的BeanDefinitionReader,再调用reader的loadBeanDefinitions方法,追进去看AbstractBeanDefinitionReader类,在这里实现了:

这里可以看出在BeanDefinitionReader中注入了一个ResourceLoader,此处可以看到对ResourceLoader调用了getResource方法,这样就可以完成对该方法的整个追溯过程了

要注意到FileSystemXmlApplicationContext本身就是一个DefaultResourceLoader的子类,其实FileSystemXmlApplicationContext中的getResource就是给父类作为模板方法调用。

那么FileSystemXmlApplicationContext是在什么地方定义了BeanDefinition的读入器BeanDefinitionReader并完成BeanDefinition的读入呢?

BeanDefinition的载入

先回到IOC容器的初始化入口,也就是看一下refresh方法,这个方法的最初是在FileSystemXmlApplicationContext的构造函数中被调用的,它的调用标志着容器初始化的开始,这些初始化对象就是BeanDefinition数据。

对容器的启动来说,refresh是一个很重要的方法。该方法在AbstractApplicationContext类中找到,它详细的描述了整个ApplicationContext的初始化过程。

我们去看FileSystemXmlApplicationContext的基类AbstractRefreshableApplicationContext是怎样实现的。 首先看他的refreshBeanFactory方法:

首先创建其需要注入的DefaultListableBeanFactory,接着调用loadBeanDefinitions来载入资源进入其所持有的BeanFactory,而这个方法在该类中是一个空实现,留给子类去做对应的实现。 而在AbstractXMLApplicationContext中,我们找到了对应的具体实现:

这里可以清楚的看到读入器的创建和配置,这里AbstractXMLApplicationContext将DefaultListableBeanFactory作为参数注入了BeanDefinitionReader,但注意,其注入是以BeanDefinitionRegistry接口的形式:

父类AbstractBeanDefinitionReader构造器:

如果传入的BeanFactory只实现了BeanDefinitionRegistry,那就注入,如果还实现了ResourceLoader,就把它作为默认的ResourceLoader,如果实现了EnvironmentCapable,会把其内部的环境也加载进来。 然后我们回过来继续看其子类XMLBeanDefinitionReader,其重载了loadBeanDefinitions方法,不使用父类的实现

这里调用了doLoadBeanDefinition方法:

该方法一共就两步,一步是加载dom,另一步是从dom将BeanDefinition注册到BeanFactory中。

在注册的方法中,是通过调用BeanDefinitionDocumentReader进行的,实际调用的是其实现类DefaultBeanDefinitionDocumentReader,在其方法中又是通过其持有的BeanDefinitionParserDelegate进行的。

总结一下流程:可以看到,在初始化FileSystemXMLApplicationContext的过程中是通过调用IOC容器的refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义的XMLBeanDefinitionReader来完成。同时,我们也知道实际使用的IOC容器是DefaultListableBeanFactory,具体的Resource载入在XMLBeanDefinitionReader读入BeanDefinition时实现。在XMLBeanDefinitionReader的实现中可以看到,是在reader.loadBeanDefinition中开始进行BeanDefinition的载入,而这时其父类AbstractBeanDefinitionReader已经为BeanDefinition的载入做好了准备。

这里调用的是loadBeanDefinitions(Resource res)方法,但这个方法在该类中是没有实现的,具体实现在XMLBeanDefinitionReader中。在读取器中,需要得到代表 xml文件的Resource,因为这个Resource对象封装了对xml文件的I/O操作,所以读取器可以在打开IO流后得到xml的文件对象,有了这个文件对象后,就可以按照Spring的Bean定义规则来对这个xml的文档树进行解析。

BeanDefinition的载入分成两部分,首先通过调用xml的解析器得到document对象,但这些对象并没有按照Spring的Bean规则进行解析。在完成通用的xml解析以后,才是按照Spring的Bean规则进行解析的地方,这个按照Spring的Bean规则进行解析的过程是在DocumentReader中实现的。这里使用的是默认的DefaultBeanDefinitionDocumentReader。这个DefaultBeanDefinitionDocumentReader的创建是在后面的方法中完成的,然后再完成BeanDefinition的处理,处理的结果由BeanDefinitionHolder对象来持有。这个BeanDefinitionHolder除了持有BeanDefinition对象外,还持有其他与BeanDefinition的使用相关的信息,比如Bean的名字、别名集合等。这个BeanDefinitionHolder的生成是通过对Document文档树的内容解析来完成的,可以看到这个解析过程是由BeanDefinitionParserDelegate来实现(具体在上面的processBeanDefinition方法中)。

解析过程: 调用delegate的parseBeanDefinitionElement方法:

在高亮的这一行进行对bean元素的详细解析:

再往里面就是各种元素的解析了,太细了以后慢慢看。

经过这样逐层的解析,我们在xml文件中定义的BeanDefinition就被整个载入到了IOC容器中,并在容器中建立了数据映射。在IOC容器中建立了对应的数据结构,或者说可以看成是pojo对象在IOC容器中的抽象,这些数据结构可以以AbstractBeanDefinition为入口,让IOC容器执行索引、查询和操作。经过以上过程,IOC容器大致完成了管理Bean对象的数据准备工作。但是,重要的依赖注入实际上在这个时候还没发生。现在IOC容器BeanDefinition中存在的还只是一些静态的配置信息。严格的说这时候容器还没有完全起作用,要完全发挥容器的作用,还需完成数据向容器的注册。

BeanDefinition的注册

在BeanDefinition的载入和解析完成后,用户定义的BeanDefinition信息已经在IOC容器内建立了自己的数据结构以及相应的数据表示,但此时这些数据还不能供IOC容器直接使用,需要在IOC中对这些BeanDefinition数据进行注册。在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的,这个HashMap的定义在DefaultListableBeanFactory中可以看到。

将解析得到的BeanDefinition向IOC容器中的map注册的过程是在载入BeanDefinition完成后进行的,调用过程如下:

具体实现: DefaultBeanDefinitionDocumentReader中:

BeanDefinitionReaderUtils中:

调用了注入的registry(即DefaultListableBeanFactory): 看DefaultListableBeanFactory中的registerBeanDefinition方法:

到这里注册就已经完成了。

转载于:https://juejin.im/post/5cc575aff265da03ba0e356b

你可能感兴趣的文章
阶段性放弃 wxPython 前的总结
查看>>
Fegla and the Bed Bugs 二分
查看>>
linux 文本处理
查看>>
swoole重启机制(转载)
查看>>
hadoop day 1
查看>>
HDU 1251 统计难题
查看>>
用javascript脚本实现微信定时发送信息
查看>>
MongoDB学习笔记(四) 用MongoDB的文档结构描述数据关系
查看>>
[Windows Azure] Data Management and Business Analytics
查看>>
java面试题07
查看>>
什么是面向对象思想
查看>>
Quick-cocos2d-x3.3 Study (十六)--------- 碰撞检测,事件监听,设置掩码
查看>>
tomcat 安装
查看>>
C#调用c++创建的dll
查看>>
12.02个人博客
查看>>
Notification通知代码简洁使用
查看>>
UIView 动画
查看>>
ssh加密公私钥
查看>>
快速部署Python应用:Nginx+uWSGI配置详解
查看>>
mybatis-generator生成数据对象
查看>>