二十六、加载Bean总结
本文最后更新于:2022年6月11日 下午
开头十几篇分析了一个配置文件经历哪些过程转变成BeanDefinition
对象,但这不是真正的bean,它仅仅包含了我们所需bean的信息。
之后十篇分析了bean的加载,此处做个总结。
bean的初始化起点,在我们第一次(显示或者隐式)调用getBean()
来开启:
1 |
|
doGetBean()
方法的主要过程:
- **转换 beanName **。因为我们调用
getBean(...)
方法传入的name
并不一定就是 beanName,可以传入 aliasName,FactoryBean,所以这里需要进行简单的转换过程。 - 尝试**从缓存中加载单例 bean **。
- bean 的实例化。
- 原型模式的依赖检查。因为 Spring 只会解决单例模式的循环依赖,对于原型模式的循环依赖都是直接抛出
BeanCurrentlyInCreationException
异常。 - 尝试从 parentBeanFactory 获取 bean 实例。如果
parentBeanFactory != null && !containsBeanDefinition(beanName)
则尝试从 parentBeanFactory 中获取 bean 实例对象,因为!containsBeanDefinition(beanName)
就意味着定义的 xml 文件中没有 beanName 相应的配置,这个时候就只能从parentBeanFactory
中获取。 - 获取 RootBeanDefinition,并对其进行合并检查。从缓存中获取已经解析的 RootBeanDefinition 。同时,如果父类不为
null
的话,则会合并父类的属性。 - 依赖检查。某个 bean 依赖其他 bean ,则需要先加载依赖的 bean。
- 对不同的 scope 进行处理。
- 类型转换处理。如果传递的
requiredType
不为null
,则需要检测所得到 bean 的类型是否与该requiredType
一致。如果不一致则尝试转换,当然也要能够转换成功,否则抛出BeanNotOfRequiredTypeException
异常。
更加简单的总结:
- 从缓存中获取 bean。
- 创建 bean 实例对象。
- 从 bean 实例中获取对象。
从缓存中获取 bean
Spring 中根据 scope 可以将 bean 分为以下几类:singleton、prototype 和 其他,这样分的原因在于 Spring 在对不同 scope 处理的时候是这么处理的:
- singleton :在 Spring 的 IoC 容器中只存在一个对象实例,所有该对象的引用都共享这个实例。Spring 容器只会创建该 bean 定义的唯一实例,这个实例会被保存到缓存中,并且对该bean的所有后续请求和引用都将返回该缓存中的对象实例。
- prototype :每次对该bean的请求都会创建一个新的实例
- 其他:
- request:每次 http 请求将会有各自的 bean 实例。
- session:在一个 http session 中,一个 bean 定义对应一个 bean 实例。
- global session:在一个全局的 http session 中,一个 bean 定义对应一个 bean 实例。
所以,从缓存中获取的 bean 一定是 singleton bean,这也是 Spring 为何只解决 singleton bean 的循环依赖。
调用 getSingleton(String beanName)
方法,从缓存中获取 singleton bean。
1 |
|
该方法就是从 singletonObjects
、earlySingletonObjects
、 singletonFactories
三个缓存中获取,这里也是 Spring 解决 bean 循环依赖的关键之处。可看《从单例缓存中获取单例 bean》、《循环依赖处理》。
创建Bean实例对象
如果缓存中没有,也没有 parentBeanFactory
,则会调用 createBean(String beanName, RootBeanDefinition mbd, Object[] args)
方法,创建 bean 实例。该方法主要是在处理不同 scope 的 bean 的时候进行调用。
1 |
|
该方法是定义在 AbstractBeanFactory 中的抽象方法,其含义是根据给定的 BeanDefinition 和 args
实例化一个 bean 对象。如果该 BeanDefinition 存在父类,则该 BeanDefinition 已经合并了父类的属性。所有 Bean 实例的创建都会委托给该方法实现。
该抽象方法的默认实现是在类 AbstractAutowireCapableBeanFactory 中实现,该方法其实只是做一些检查和验证工作,真正的初始化工作是由 doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
方法来实现。
1 |
|
doCreateBean(...)
方法,是创建 bean 实例的核心方法,主要流程如下:
<1>
处,如果是单例模式,则清除factoryBeanInstanceCache 缓存
,同时返回 BeanWrapper 实例对象,当然如果存在。<2>
处,如果缓存中没有 BeanWrapper 或者不是单例模式,则调用createBeanInstance(...)
方法,实例化 bean,主要是将 BeanDefinition 转换为 BeanWrapper 。<3>
处,MergedBeanDefinitionPostProcessor 的应用。<4>
处,单例模式的循环依赖处理。<5>
处,调用populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw)
方法,进行属性填充。将所有属性填充至 bean 的实例中。<6>
处,调用initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)
方法,初始化 bean 。<7>
处,依赖检查。<8>
处,注册 DisposableBean 。
实例化Bean
如果缓存中没有 BeanWrapper 实例对象或者该 bean 不是 singleton,则调用 createBeanInstance(...)
方法。创建 bean 实例。
1 |
|
实例化 Bean 对象,主要过程:
<1>
处,如果存在 Supplier 回调,则调用obtainFromSupplier(Supplier instanceSupplier, String beanName)
方法,进行初始化。<2>
处,如果存在工厂方法,则使用工厂方法进行初始化,并直接return。<3>
处,首先判断缓存,如果缓存中存在,即已经解析过了,则直接使用已经解析了的。根据constructorArgumentsResolved
参数来判断:<3.1>
处,是使用构造函数自动注入,即调用autowireConstructor(String beanName, RootBeanDefinition mbd, Constructor[] ctors, Object[] explicitArgs)
方法。<3.2>
处,还是默认构造函数,即调用instantiateBean(final String beanName, final RootBeanDefinition mbd)
方法。
<4>
处,如果缓存中没有,则需要先确定到底使用哪个构造函数来完成解析工作,因为一个类有多个构造函数,每个构造函数都有不同的构造参数,所以需要根据参数来锁定构造函数并完成初始化。<4.1>
处,如果存在参数,则使用相应的带有参数的构造函数,即调用autowireConstructor(String beanName, RootBeanDefinition mbd, Constructor[] ctors, Object[] explicitArgs)
方法。<4.2>
处,否则,使用默认构造函数,即调用instantiateBean(final String beanName, final RootBeanDefinition mbd)
方法。
其实核心思想还是在于根据不同的情况执行不同的实例化策略,主要是包括如下四种策略:
- Supplier 回调
instantiateUsingFactoryMethod(...)
方法,工厂方法初始化autowireConstructor(...)
方法,构造函数自动注入初始化instantiateBean(...)
方法,默认构造函数注入
其实无论哪种策略,他们的实现逻辑都差不多:确定构造函数和构造方法,然后实例化。
只不过相对于 Supplier 回调和默认构造函数注入而言,工厂方法初始化和构造函数自动注入初始化会比较复杂,因为他们构造函数和构造参数的不确定性,Spring 需要花大量的精力来确定构造函数和构造参数,如果确定了则好办,直接选择实例化策略即可。当然在实例化的时候会根据是否有需要覆盖或者动态替换掉的方法,因为存在覆盖或者织入的话需要创建动态代理将方法织入,这个时候就只能选择 CGLIB 的方式来实例化,否则直接利用反射的方式即可。
属性填充
属性填充其实就是将 BeanDefinition 的属性值赋值给 BeanWrapper 实例对象的过程。在填充的过程需要根据注入的类型不同来区分是根据类型注入还是名字注入,当然在这个过程还会涉及循环依赖的问题的。
1 |
|
主要过程:
<1>
,根据hasInstantiationAwareBeanPostProcessors
属性来判断,是否需要在注入属性之前给 InstantiationAwareBeanPostProcessors 最后一次改变 bean 的机会。此过程可以控制 Spring 是否继续进行属性填充。统一存入到 PropertyValues 中,PropertyValues 用于描述 bean 的属性。
<2>
,根据注入类型(AbstractBeanDefinition.getResolvedAutowireMode()
方法的返回值 )的不同来判断:是根据名称来自动注入autowireByName()
,还是根据类型来自动注入autowireByType()
。<3>
,进行 BeanPostProcessor 处理。<4>
,依赖检测。
<5>
,将所有 PropertyValues 中的属性,填充到 BeanWrapper 中。
初始化Bean
初始化 bean 为 createBean(...)
方法的最后一个过程:
1 |
|
初始化 bean 的方法其实就是三个步骤的处理,而这三个步骤主要还是根据用户设定的来进行初始化,这三个过程为:
<1>
激活 Aware 方法。<3>
后置处理器的应用。<2>
激活自定义的 init 方法。
从Bean实例中获取对象
无论是从单例缓存中获取的 bean 实例 还是通过 createBean(...)
方法来创建的 bean 实例,最终都会调用 getObjectForBeanInstance(...)
方法来根据传入的 bean 实例获取对象,按照 Spring 的传统,该方法也只是做一些检测工作,真正的实现逻辑是委托给 getObjectFromFactoryBean(...)
方法来实现。代码如下:
1 |
|
主要流程:
- 若为单例且单例 Bean 缓存中存在
beanName
,则<1>
进行后续处理(跳转到下一步),否则,则<2>
从 FactoryBean 中获取 Bean 实例对象。 <1.1>
首先,获取锁。其实我们在前面篇幅中发现了大量的同步锁,锁住的对象都是this.singletonObjects
,主要是因为在单例模式中必须要保证全局唯一。<1.2>
然后,从factoryBeanObjectCache
缓存中获取实例对象object
。若object
为空,则调用doGetObjectFromFactoryBean(FactoryBean factory, String beanName)
方法,从 FactoryBean 获取对象,其实内部就是调用FactoryBean.getObject()
方法。<1.3>
如果需要后续处理(shouldPostProcess = true
),则进行进一步处理:- 若该 Bean 处于创建中(
isSingletonCurrentlyInCreation(String beanName)
方法返回true
),则返回非处理的 Bean 对象,而不是存储它。 - 调用
beforeSingletonCreation(String beanName)
方法,进行创建之前的处理。默认实现将该 Bean 标志为当前创建的。 - 调用
postProcessObjectFromFactoryBean(Object object, String beanName)
方法,对从 FactoryBean 获取的 Bean 实例对象进行后置处理。 - 调用
afterSingletonCreation(String beanName)
方法,进行创建 Bean 之后的处理,默认实现是将该 bean 标记为不再在创建中。
- 若该 Bean 处于创建中(
<1.4>
最后,加入到factoryBeanObjectCache
缓存中。