作为一个java程序员,保守估计一年里也都有300天要和Spring有亲密接触~~像我这种怕是每天都要撸撸Spring,所以这次也要做个深入了解!这次就来看看Spring是怎么初始化IoC容器的😄
我们都是知道Spring为企业级应用提供了丰富的功能,而这些功能底层都依赖于底层最核心的两种特性IOC和AOP。
IOC实现里主要包括两部分,一个是IOC容器初始化,另外一个是依赖注入,由于两部分是相对独立的部分,所以分成不同文章讲解,本篇主要讲述IOC容器的初始化。
一、IoC的概念
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
上面这个概念是来自维基百科,在Expert Spring MVC and Web Flow和Expert One-on-One J2EE without EJB等书中都也是将依赖注入看作是IoC的一种方式。不过有些地方会把IoC和DI看成一个概念(例如Spring in Action、Spring揭密等书),不过没关系,不影响我们理解就可以。
A类的正常的运行需要B类
没有IoC之前我们在使用A类的某个方法时,总会依赖于B类的一些功能,这样我们就要去new个B对象,有时还要考虑什么时候销毁,什么时候使用单例模式等等,类少了还好管理,这要是多起来真是再聪明的人怕也是要十个头九个大了, 而且A、B之间的依赖关系使各代码紧密耦合,一旦B类的出现问题,或者某天干脆不用B了用C,那是不是A类里的new B()全得换成new C()?想象都觉得累…
有了IoC之后,对象创建都交给第三方容器处理,A中的B通过注入来完成,B出问题了,或者需要换成C了,只要把注入的B换成C就可以(现实开发中B、C可以实现相同的接口解决~所以啊,Spring是面向接口编程鸭)。
- Expert One-on-One J2EE without EJB这本书是spring爸爸Rod Johnson写的,进入Spring的BeanFactory类里面看看作者就是他,哈哈!
- 浅谈控制反转与依赖注入:这是我看过最好的一篇对控制反转的解释,强烈推荐!
二、IoC容器初始化
预备内容
本节只讲解IoC容器的初始化,其中包括创建容器和将bean装入到容器中,下面这三件事是该部分的核心:
BeanDefinition的Resource定位
BeanDefinition的载入和解析
BeanDefinition在容器中的注册
因为Spring的IoC容器实现太复杂了,各种类之间的调用很容易就让我们陷入到细节之中,结果就走的太远忘记了为啥要出发了😓,本文主要将述容器初始化时最主要的三件事。
先了解几个概念:
BeanFactory:这是IOC容器的接口定义,提供了IoC最基本的功能,如果说容器是个汽车工厂,那么这个结构就规定了汽车工厂最基本的功能,能储存零件,能组装汽车。
1 | public interface BeanFactory { |
ApplicationContext:升级版汽车厂,除了上面的功能,还提供很多人性化的服务,继承了 MessageSource,ResourceLoader,ApplicationEventPublisher等等接口,在BeanFactory 简单IoC容器的基础上添加了许多对高级容器的支持。
1 | public interface ApplicationContext extends EnvironmentCapable,ListableBeanFactory,HierarchicalBeanFactory,MessageSource,ApplicationEventPublisher,ResourcePatternResolver { |
这里面的方法也不多,主要的方法都在继承的接口里
BeanDifinition:储存 Spring中 Bean的信息,包括各属性名,类名,是否单例等,抽象了我们对 Bean的定义,是让容器起作用的主要数据类型。对 IOC 容器来说,BeanDefinition 就是对控制反转模式中管理的对象依赖关系的数据抽象。
接下来正式进入IoC容器初始化的分析,以FileSystemXmlApplicationContext为例,下面是FileSystemXmlApplicationContext的继承关系~(这形状,满满的爱啊,哈哈)
BeanDefinition的Resource定位
1 | public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { |
看以看出,本类对所有configLocation都进行了处理,使所有以xml形式存在的BeanDefinition都得到了处理,其中这个refresh就最最关键点方法,接下来对refresh进行解析。
refresh是在AbstractApplicationContext中实现,理解了refresh方法,基本就理解了IoC初始化的全过程了。
1 | public void refresh() throws BeansException, IllegalStateException { |
接下来我们详细来看一下容器的构建过程,在类AbstractRefreshableApplicationContext中
1 |
|
1 |
|
loadBeanDefinitions在AbstractRefreshableApplicationContext中是个抽象方法,我直接找到了在子类AbstractXmlApplicationContext(其实有三个实现类,但是我们现在研究的是FileSystemXmlApplicationContext)中的实现。
1 | protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { |
这次是跳转到AbstractXmlApplicationContext里面,继续阅读
1 | protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { |
AbstractBeanDefinitionReade中的方法,就是在这里进行加载的
1 |
|
还在本类里~不用跳了
1 | public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { |
refresh方法完成IoC的整个初始化,其中 refreshBeanFactory()方法非常的重要,本小节讲到定位,下一小节开始讲解BeanDefinition解析与加载。
BeanDefinition的载入和解析
对于IoC容器来说,这个载入过程相当于把xml中的BeanDefinition转换成一个Spring内部的数据结构的过程。IoC容器对Bean的管理和依赖注入功能是通过对其持有的BeanDefinition进行各种相关操作来实现的。这些BeanDefinition是通过一个HashMap来实现的。
承接上文,loadBeanDefinitions()是对BeanDefinition载入和解析的核心方法。具体实现在XMLBeanDefinitionReader里面。
1 | public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { |
1 | public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { |
1 | public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { |
这个是在XMLBeanDefinitionReader中实现(没有进其他类)
1 | protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) |
仍然在本类中
1 | public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { |
进入DefaultBeanDefinitionDocumentReader类中,在文档元素中获取根元素,并继续调用doRegisterBeanDefinition进行注册。
1 | public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { |
1 | protected void doRegisterBeanDefinitions(Element root) { |
1 | // 不难看出,这是对xml的解析过程 |
1 | private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { |
1 | protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { |
至此,XML中的BeanDefinition的解析和载入全部完成,接下来进入bean的注册部分。
BeanDefinition在IoC容器中的注册
经过定位和载入后,BeanDefinition已经在IoC建立起相应的数据结构,为了更友好的使用这些BeanDefinition,需要在IoC容器中将这些BeanDefinition进行注册。
该方法在BeanDefinitionReaderUtils类中
1 | public static void registerBeanDefinition( |
跳转到DefaultListableBeanFactory类中,前面创建工厂时用的就是这个工厂
1 | public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) |
到这里,注册完成。我们创建bean工厂,将BeanDefinition注册到了IoC容器持有的Map中。这些信息是控制反转的基础。
三、小结
本文开始简略解释IoC的概念,然后从FileSystemXmlApplicationContext着手,根据源码一步一步讲述从bean工厂创建到BeanDefinition在IoC中注册核心逻辑。Spring源码确实细节太多,在阅读源码过程中,一定要抓住核心逻辑。
本文是博主在学习Spring源码过程中对IoC的总结,希望对想要阅读源码但不知从何下手的同学有所帮助!如有错误希望大家指正。
本文首发于cdream个人博客
欢迎转载,转载请注明出处!
本文参考: