本文的内容为对Spring IoC容器实现的分析。
本文一共分为5个部分:
- 第一部分简要讲述了IoC的概念
- 第二部分对Spring IoC容器中的主要类及其职责做一些了解
- 第三部分分析了Spring IoC容器的初始化过程
- 第四部分分析了从Spring IoC容器中获取Bean的过程
- 第五部分简要讲述了Spring IoC容器对Bean生命周期的管理。
本文假设读者对以下的概念有所了解:IoC(控制反转),DI(依赖注入),Bean,并且读者有使用Spring IoC容器的经验。
约定:本文中所指的IoC容器没有特别说明均为Spring IoC容器
一、什么是IoC
IoC是Inversion of Control的缩写,中文的意思是控制反转,在IoC中,组件不需要去寻找它所依赖的对象,而是由IoC容器来负责将组件所依赖的对象通过Java Bean的Setter方法或者是构造函数等方式注入给组件。IoC的另一个名字是DI,即依赖注入,关于IoC和DI之间的关系以及关于IoC的更多内容,大家可以参考Wiki上的控制反转条目。
二、IoC容器中的类主要类及其职责
我们先来看下IoC容器的一个大概的类图:
这张图中比较简单的展示了IoC容器中的各个类及其指责,我们需要重点把握几个接口的职责:
- BeanFactory:这个接口是整个IoC容器最底层的接口,定义了一组访问Bean容器的基本方法。一些其他的接口,比如ListableBeanFactory和ConfigurableBeanFactory,都是继承了BeanFactory,并添加了其他的方法来完成某些特别的功能(比如ConfigurableBeanFactory,顾名思义,这个接口的职责是让BeanFactory变得可配置,那么它就定义了一组可以配置BeanFactory的方法)。
- AbstractBeanFactory:从名字可以看出,这个类是BeanFactory接口的一个抽象实现类,这个类本身实现的是ConfigurableBeanFactory,对ConfigurableBeanFactory以及BeanFactory中的方法提供了实现,并且提供了一些诸如单例缓存,别名等等功能。
- SingletonBeanRegistry:定义了一组操作单例Bean的方法
三、IoC容器的初始化
使用过Spring的人都知道,我们都是在一份Bean配置文件中定义Bean,然后就可以通过BeanFactory的getBean()方法来获取Bean,那么我们就可以大致猜想到IoC容器的初始化工作大概就是将我们编写的Bean配置文件转换成IoC容器内部定义的用于放置Bean定义信息的数据结构,而这个数据结构就是BeanDefinition这个类。下面我们就来了解下这个转换过程是如何进行的。
我们通过实例化ClassPathXmlApplicationContext这个类来一步步来看其初始化的过程,简单地实例化ClassPathXmlApplicationContext的代码如下:
这里我们传入一个beans.xml作为配置文件的路径去实例化一个ClassPathXmlApplicationContext。
首先我们还是来看下整个初始化过程的序列图:
这个图中涉及到的类或许有点吓人,且慢,下面我会慢慢带你了解整个过程。从序列图里面我们看到初始化过程首先调用了refresh()方法,后面调用到了AbstractXmlApplicationContext的loadBeanDefinitions方法,来看下这个方法的实现:
这个方法将beanFactory(实现了BeanDefinitionRegistry接口,后续将通过这个接口将Bean定义信息注册到BeanFactory中去)传入new了一个XmlBeanDefinitionReader对象,然后将刚刚new出来的beanDefinitionReader传入调用loadBeanDefinitions方法,最终调用了XmlBeanDefinitionReader的loadBeanDefinitions(EncodedResource encodedResource)方法:
这个方法获取了Bean配置文件的输入流,并且调用了doLoadBeanDefinitions方法,在这个方法里面,程序将输入流转换成Document对象,然后调用了下面这个方法:
需要注意这个createReaderContext(resource)方法,创建这个方法的时候XmlBeanDefinitionReader将自己传入,以便在后面可以获取到它的Registry对象。最终DefaultBeanDefinitionDocumentReader将解析BeanDefinition的工作又交给了BeanDefinitionParserDelegate对象:
从上面的方法中,我们可以看到BeanDefinitionParserDelegate对象解析出BeanDefinition后,就由BeanDefinitionRegistry来将BeanDefinition注册到BeanFactory中去了。
至此,整个BeanFactory就初始化完毕了,可能一大堆方法调来调去地早就把大家给调晕了,我们就来总结下初始化过程中设计到的几个主要的类以及它们的职责吧:
- XmlBeanDefinitionReader:读取定义Bean的XML文件并且将XML文件转成Document对象,交给BeanDefinitionDocuementReader再做解析。它持有一个BeanDefinitionRegistry对象,用于将BeanDefinition注册到BeanFactory中去。
- XmlBeanDefinitionDocumentReader:取出Docuement对象内的各个元素并将这些元素交给BeanDefinitionParserDelegate来解析。
- BeanDefinitionParserDelegate:用于解析Bean定义信息的代理类,负责一个Bean定义信息(可以看作是一个<bean></bean>标签对)解析成一个BeanDefinition对象。
- BeanDefinitionRegister:负责将BeanDefinition注册到BeanFactory中去。
四、从IoC容器中获取Bean
用过Spring的人大概都知道,在Spring中,我们是通过调用BeanFactory的getBean()方法来取得我们所需要的Bean的,而getBean()方法的主要逻辑在AbstactBeanFactory的doGetBean()方法中。在Spring中,有单例Bean和原型Bean的区分,在从容器中获取Bean的时候,单例Bean和原型Bean有些不同,当第一次获取单例Bean的时候,整个过程和获取原型Bean几乎是一样的,都需要创建一个Bean,但是当第二次,第三次,……,获取同样的单例Bean的时候,容器就直接从单例缓存中获取Bean了,而不会再去像获取原型Bean一样一而再再而三地创建Bean了。这样我们这一节也主要从两个方面来讲,一个是获取原型Bean,第一次获取单例Bean的逻辑和这个类似,有特别的地方也会在这里顺带提到,二则是将从单例缓存中获取单例Bean的过程,首先我们来看获取原型Bean:
4.1、获取原型Bean
正如前面所说,我们来看下AbstractBeanFactory的doGetBean()方法来了解获取原型Bean的整个过程。
在获取Bean的时候,无论这个Bean是单例的还是原型的,Spring都会尝试从单例缓存中获取Bean,但是当拿原型Bean的时候,这里显然是拿不到的,接下来程序就会根据BeanDefinition信息来判断要创建的Bean是不是原型Bean,如果是,则进入下面这段逻辑:
程序在上图的(1)中的位置调用了beforePrototypeCreation方法,告诉容器当前的这个Bean正在创建中,来防止发生重复创建的情况。接下来,程序在(2)处调用了createBean方法来创建这个prototypeBean,最后,在(3)处,程序调用afterProtytypeCreation来告诉容器,这个Bean现在已经不再创建过程中了。
那么,让我们来关注下createBean这个方法里面干了些什么事情:
在做了一堆准备工作后,程序就到了上面的这一段中,从代码中我们可以看出这个方法主要的功能为以下两点:
- 调用resolveBeforeInstantiation方法,让BeanPostProcessor可以有机会给你返回一个代理类而不是原来的类,当后面我们看到Spring AOP代理类的生成的时候,就会看到这个方法的用处了。
- 调用doCreateBean()方法创建Bean
我们再来看下doCreateBean方法:
同样,这个方法里面也有两个主要的功能:
- 一是调用createBeanInstance方法,创建一个Bean实例,在这个方法的内部,会调用BeanUtils的instantiateClass来实例化Bean,并把它包装成一个BeanWrapper
- 二是调用populateBean方法来将Bean依赖的属性设置进去。
4.2、创建单例Bean
整个获取原型Bean的过程大概就是这样样子,因为创建单例Bean和这个过程基本上是一样的,但是也有一些稍微不一样的地方,这里也稍微提到一下:
创建单例Bean是通过调用getSingleton来实现的,这个方法传入一个beanName和一个ObjectFactory,这个ObjectFactory的getObject方法里面调用到了我们前面提到的createBean方法,所以我们看下getSingleton这个方法的实现:
这个方法先尝试从singletonObjects中获取单例Bean,如果获取不到,则自己创建,同样,和创建原型Bean一样,在创建开始之前会调用beforeSingletonCreation方法来将beanName放到singletonsCurrentlyInCreation来告诉容器这个Bean已经在创建中了,在创建完成之后,会将BeanName从singletonsCurrentlyInCreation中删除掉。创建的过程是调用了传入的ObjectFactory的getObject方法,和创建原型Bean类似。在创建完成之后,还有一步addSingleton的操作,来讲单例放到单例缓存中去,看一下这个的实现:
方法的逻辑非常简单:把创建出来的单例Bean放到singletonObjects中去,然后从singletonFactories和earlySingletonObjects中删除掉,最后在registeredSingletons里面再加入这个Bean,对于这里面用到的几个容器,我觉得有必要在这里描述一下其作用,要不然读者肯定是晕呼晕呼的:
- singletonObjects:用于保存BeanName和Bean实例之间的关系
- singletonFactories:用于保存BeanName和创建Bean的工厂之间的关系
- earlySingletonObjects:也是保存BeanName和Bean实例之间的关系,与singletonObjects的不同之处在于,当一个单例Bean被放到这里面去后,那么当Bean还在创建过程中,就可以通过getBean来拿到了,其目的是用来检测循环引用。
- registeredSingletons:用来保存当前所有已注册的Bean
4.3、从单例缓存中获取单例Bean
单例在Spring的同一个容器内只会被创建一次,后续再获取Bean,就直接从单例缓存中获取了,我们来看下这一段过程,看下doGetBean里面调用的getSingleton方法:
这个方法的逻辑也相对简单,先尝试从singletonObjects里面获取,如果获取不到再从earlySingletonObjects里面获取,如果再获取不到,再尝试从singletonFactories里面获取beanName对应的ObjectFactory,然后调用这个ObjectFactory的getObject来创建Bean,并放到earlySingletonObjects里面去,并且从singletonFacotories里面remove掉这个ObjectFactory。
六、IoC容器对Bean生命周期的管理
Spring有一套Bean生命周期去管理Bean,值得注意的是,Spring只对非单例的Bean进行生命周期管理。关于Spring中Bean的生命周期,我们来看下一张老图:
在上面这张图里面,我们看到有很多的生命周期方法,那么这些生命周期方法是在哪里调用的呢?在前面获取原型Bean的一节中,我们已经知道,Spring会先调用createBeanInstance方法来创建Bean实例,然后通过populateBean方法来设置Bean的属性,在调用这个方法之后,其实Spring还调用了一个initializeBean的方法,上图中我们看到的生命周期方法基本上都在这个方法里面调用:
在这个方法里面:
- Spring首先调用了invokeAwareMethods来调用各个Aware方法,包括BeanNameAware,BeanClassLoaderAware和BeanFactoryAware
- 然后调用了所有BeanPostProcessor的postProcessBeforeInitialization方法
- 接着调用invokeInitMethods方法,里面包括调用afterPropertiesSet和自定义的init方法
- 最后调用了所有BeanPostProcessor的postProcessAfterInitialization方法
在调用这些方法以后,我们的Bean才算是可以使用啦。至于生命周期的最后两个方法,是在容器销毁的时候来调用的。