文章目录
前面六节,详细梳理了IOC容器刷新的全部步骤,以及一个重要的后置处理器ConfigurationClassPostProcessor,详见:
SpringBoot源码解读与原理分析(二十)IOC容器的刷新(一)
SpringBoot源码解读与原理分析(二十一)IOC容器的刷新(二)
SpringBoot源码解读与原理分析(二十二)IOC容器的刷新(三)ConfigurationClassPostProcessor
SpringBoot源码解读与原理分析(二十三)IOC容器的刷新(四)
SpringBoot源码解读与原理分析(二十四)IOC容器的刷新(五)
SpringBoot源码解读与原理分析(二十四)IOC容器的刷新(六)
本节是IOC容器刷新阶段的最后一篇,梳理一下循环依赖的解决方案。
7.15 循环依赖的解决方案
IOC容器初始化bean对象的逻辑中可能会遇到bean对象之间循环依赖的问题。当问题出现时,IOC容器内部可以恰当地予以解决。
7.15.1 循环依赖问题的产生
循环依赖,简单理解就是两个或多个bean对象之间的互相引用(互相持有对方的引用)。
例如,人(Person)与猫(Cat)之间相互引用,人养猫,猫依赖人。
SpringFramework会针对不同类型的循环依赖实行不同的处理策略。
7.15.2 循环依赖的解决模型
IOC容器内部对于解决循环依赖主要使用了三级缓存的设计:
- singletonObjects:一级缓存,存放完全初始化好的bean对象,从这个集合中提取出来的bean对象可以立即返回。
- earlySingletonObjects:二级缓存,存放创建好但没有初始化属性的bean对象,它用来解决循环依赖。
- singletonFactories:三级缓存,存放单实例BeanFactory。
- singletonCurrentlyInCreation:存放正在被创建的bean对象。
代码清单1:DefaultSingletonBeanRegistry.java
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
由 代码清单1 可知,上述成员均在DefaultListableBeanFactory的父类DefaultSingletonBeanRegistry中,该父类本身是一个单实例bean对象的管理容器。
7.15.3 基于setter/@Autowired的循环依赖
基于setter/@Autowired的循环依赖,是SpringFramework可以解决的一种循环依赖。
7.15.3.1 编写测试代码
代码清单2
public class Person {
@Autowired
private Cat cat;
}
public class Cat {
@Autowired
private Person person;
}
public class Test03App {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.xiaowd.springboot.aop.test03");
}
}
7.15.3.2 初始化Cat
由于在字母表中cat比person的首字母靠前,所以IOC容器会先初始化cat对象。
(1)getSingleton中的处理
在 SpringBoot源码解读与原理分析(二十四)IOC容器的刷新(五) 7.11.2.7 getSingleton控制单实例对象 中,getSingleton
方法有一个特殊的步骤:beforeSingletonCreation
,这是控制循环依赖的关键步骤。
代码清单3:DefaultSingletonBeanRegistry.java
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
由 代码清单3 可知,beforeSingletonCreation
方法会检查当前正在创建的bean对象是否存在于singletonsCurrentlyInCreation集合中。
如果当前正在创建的bean对象的名称存在于该集合中,说明出现了循环依赖问题(同一个对象在一个创建流程中被创建了两次),则抛出BeanCurrentlyInCreationException异常。
如果不存在,则将当前正在创建的bean对象的名称添加到singletonsCurrentlyInCreation集合中。
(2)对象创建完毕后的处理
如果getSingleton
方法没有抛出异常,则可以顺利进入createBean
→doCreateBean
方法。在doCreateBean
方法中,createBeanInstance
方法执行完毕后,一个空的cat对象被成功创建。
在进入populateBean
之前,有一个处理循环依赖的逻辑。
代码清单4:AbstractAutowireCapableBeanFactory.java
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
由 代码清单4 可知,earlySingletonExposure变量由三部分判断结果共同计算产生,分别是:
mbd.isSingleton
:当前对象是一个单实例bean对象;this.allowCircularReferences
:IOC容器本身允许出现循环依赖(默认为true);isSingletonCurrentlyInCreation
:正在创建的单实例bean对象名称存在于singletonsCurrentlyInCreation集合中。
前两个条件在当前场景中显然为true;第三个条件,上一步已经将当前正在创建的bean对象添加到singletonsCurrentlyInCreation集合中,因此也为true,所以最终earlySingletonExposure=true,执行if结构中的addSingletonFactory
方法。
代码清单5:DefaultSingletonBeanRegistry.java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
由 代码清单5 可知,addSingletonFactory
方法会将当前正在创建的bean对象的名称保存到三级缓存singletonFactories中(注意被包装成了ObjectFactory),并从二级缓存earlySingletonObjects中移除(实际上此时二级缓存并没有cat对象的名称)。
(3)依赖注入时的处理
接下来将进入populateBean
方法中,进行属性赋值和依赖注入。
代码清单6:AutowiredAnnotationBeanPostProcessor.java
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} // catch ...
return pvs;
}
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// ...
value = resolveFieldValue(field, bean, beanName);
// ...
}
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
// ...
Object value;
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
// ...
return value;
}
代码清单7:DefaultListableBeanFactory.java
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ...
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
// ...
}
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ...
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
// ...
}
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
throws BeansException {
return beanFactory.getBean(beanName);
}
由于cat对象中使用@Autowired注解注入了person对象,因此AutowiredAnnotationBeanPostProcessor会在回调postProcessProperties
方法时将person对象提取出来并注入到cat对象中。
由 代码清单6、7 可知,依赖注入依次经过AutowiredAnnotationBeanPostProcessor的inject
、resolveFieldValue
方法,以及DefaultListableBeanFactory的resolveDependency
、doResolveDependency
、resolveCandidate
方法。当执行最后一个方法时,使用BeanFactory的getBean
方法触发person对象的初始化全流程(此时person对象并未初始化)。
7.12.3.3 初始化Person
创建person对象的过程与创建cat类似,都是执行getBean
→doGetBean
,其中包含geiSingleton
处理,以及对象创建完毕后将person对象包装成ObjectFactory放入三级缓存singletonFactories中。
到了依赖注入环节,由于person对象中使用@Autowired注解注入了cat对象,因此AutowiredAnnotationBeanPostProcessor会从BeanFactory中获取cat对象并注入。
(1)再次获取cat对象
再次获取cat对象时执行的方法依然是getBean
→doGetBean
,但在doGetBean
方法中调用getSingleton
方法时,调用的是另一个重载方法。
代码清单8:DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存获取对象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 一级缓存没有对象,且对象正在创建,则从二级缓存中取对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 二级缓存中没对象,且允许创建早期引用,则再从一级缓存中取对象
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 一级缓存中没对象,则从二级缓存中取对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 二级缓存中没对象,则从三级缓存中取对象
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 三级缓存中有对象,将bean对象放入二级缓存,并从三级缓存中移除
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
由 代码清单8 可知,如果当前获取的bean对象正在创建,并且二级缓存earlySingletonObjects没有该bean对象,三级缓存singletonFactories有该bean对象,则说明当前获取的bean对象是一个没有完成依赖注入的不完全对象。
即便当前cat对象是一个不完全的对象,但也是一个存在于IOC容器的真实对象,不影响注入,因此getSingleton
方法在判断条件成立后,将bean对象放入二级缓存,并从三级缓存中移除。
(2)person对象初始化完成后的处理
执行完 代码清单8 的getSingleton
方法后,获取到了cat对象,并注入到person对象中,后续再进行person对象的初始化逻辑。
上述步骤都执行完后,再次调用另一个重载的getSingleton
方法。
代码清单9:DefaultSingletonBeanRegistry.java
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// ...
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
} // catch ...
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
由 代码清单9 可知,afterSingletonCreation
方法的作用是将当前正在获取的bean对象名称从singletonsCurrentlyInCreation中移除,代表当前环节中该bean对象未正在创建;
而addSingleton
方法的作用是将创建好的bean对象放入一级缓存singletonObjects中,且从二级缓存earlySingletonObjects和三级缓存singletonFactories中移除,并记录已经创建的单实例bean对象。
至此,一个person对象创建流程结束。
7.12.3.4 回到Cat的创建流程
person对象创建完成后,此时cat对象的依赖注入工作尚未完成。此处会将完全创建好的person对象进行依赖注入。注入完成后,代表Cat与Person循环依赖的场景处理完毕。
后续的动作与person对象一致,最终会将cat对象放入一级缓存,从其他的缓存中移除,从而完成cat对象的创建。
7.12.3.5 小结
基于setter方法或@Autowired属性注入的循环依赖,IOC容器的解决流程如下:
(1)创建bean对象之前,将该bean对象的名称放入“正在创建的bean对象”集合singletonCurrentlyInCreation中。
(2)doCreateBean
方法中的createBeanInstance
方法执行完毕后,会将当前bean对象包装成ObjectFactory放入三级缓存singletonFactories中。
(3)对bean对象进行属性赋值和依赖注入时,触发被循环依赖的bean对象的初始化流程。
(4)被循环依赖的bean对象创建时,会检查三级缓存singletonFactories中是否存在、且二级缓存earlySingletonObjects中是否不存在该bean对象。如果三级缓存中存在且二级缓存中不存在该bean对象,则会将三级缓存中的bean对象移入二级缓存中,并进行依赖注入。
(5)被循环依赖的bean对象创建完毕后,会将该对象放入一级缓存singletonObjects中,并从其他缓存中移除。
(6)所有循环依赖的bean对象均注入完毕后,一个循环依赖的处理流程结束。
7.15.4 基于构造方法的循环依赖
基于构造方法的循环依赖,是IOC容器无法予以处理的一种类型,只能抛出异常。
假设还以cat和person对象为例:
(1)IOC容器首先创建cat对象,由于调用cat的构造方法需要依赖person对象,从而引发person对象的创建。
(2)IOC容器创建person对象,由于调用person的构造方法需要依赖cat对象,此时cat对象还未创建出来,因而再一次引发cat对象的创建。
(3)IOC容器第二次创建cat对象时,由于第一次创建cat对象就在singletonCurrentlyInCreation集合中存放了“cat”名称,因此第二次创建cat对象时singletonCurrentlyInCreation集合中已存在“cat”名称,从而抛出BeanCurrentlyInCreationException异常,表示出现了不可解决的循环依赖。
7.15.5 基于原型Bean的循环依赖
基于原型Bean的循环依赖,也是IOC容器无法予以处理的一种类型。
以cat和person对象为例:
(1)IOC容器首先创建cat对象,之后进行person对象的依赖注入,由于person被定义为原型Bean,触发person对象的创建。
(2)IOC容器创建person对象,之后进行cat对象的依赖注入,由于cat对象也被定义为原型Bean,触发cat对象的创建。
(3)IOC容器第二次创建cat对象时,由于第一次创建cat对象就在原型Bean对象名称的集合prototypesCurrentlyInCreation中存放了“cat”名称,因此第二次创建cat对象时prototypesCurrentlyInCreation集合中已存在“cat”名称,从而抛出BeanCurrentlyInCreationException异常,表示出现了不可解决的循环依赖。
7.15.6 引入AOP的额外设计
在 7.15.3节 中提到,getSingleton
方法会将三级缓存中的bean对象放入二级缓存中,三级缓存中存放的是被封装过的ObjectFactory对象,而二级缓存中存放的是真正的bean对象,为什么会有ObjectFactory对象到bean对象之间的过渡呢?
已知的是,在bean对象创建完成后,IOC容器会指派后置处理器BeanPostProcessor对需要进行AOP增强的bean对象进行代理对象的创建。
原始的目标对象和被AOP增强后的代理对象本质上是两个完全不同的对象,IOC容器为了确保最终注入的是AOP增强后的代理对象而不是原始的目标对象,会在ObjectFactory对象到bean对象的过渡期间进行额外的检查,该环节的检查会提前创建代理对象,并替换原始对象。
代码清单10:AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// ...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// logger ...
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// ...
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
由 代码清单10 可知,getEarlyBeanReference
方法的实现是回调所有SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference
方法,该方法可以获取单实例bean对象的引用,也正是通过该方法IOC容器可以将一个普通bean对象转化为被AOP增强的代理对象。
所有实现AOP增强的后置处理器都继承自AbstractAutoProxyCreator,而它本身实现了SmartInstantiationAwareBeanPostProcessor接口,内部自然有getEarlyBeanReference
方法。
代码清单11:AbstractAutoProxyCreator.java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 必要时创建代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
由 代码清单11 可知,如果当前正在创建的bean对象的确需要创建代理对象,则会先行创建代理对象,并替换原始对象。由此,解释了为什么IOC容器解决循环依赖使用三级缓存而不是二级。
7.16 小结
第7章到此就梳理完毕了,本章的主题是:IOC容器的刷新。回顾一下本章的梳理的内容:
(二十)IOC容器的刷新(一):7.1-7.3
(二十一)IOC容器的刷新(二):7.4-7.5
(二十二)IOC容器的刷新(三):ConfigurationClassPostProcessor
(二十三)IOC容器的刷新(四):7.6-7.10
(二十四)IOC容器的刷新(五):7.11
(二十五)IOC容器的刷新(六):7.12-7.13,IOC容器刷新过程中涉及的扩展点
(二十六)IOC容器的刷新(七):循环依赖的解决方案
更多内容请查阅分类专栏:SpringBoot源码解读与原理分析
第8章主要梳理:嵌入式Web容器。主要内容包括:
- 嵌入式Tomcat容器简介;
- Tomcat的整体框架与核心工作流程;
- 嵌入式Web容器的模型设计;
- 嵌入式Web容器的初始化时机;
- 嵌入式Tomcat的回调启动。