Spring — 循环依赖
Posted CoderLi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring — 循环依赖相关的知识,希望对你有一定的参考价值。
读完这篇文章你将会收获到
Spring
循环依赖可以分为哪两种Spring
如何解决setter
循环依赖Spring
为何是三级缓存 , 二级不行 ?Spring
为啥不能解决构造器循环依赖
概述
循环依赖就是循环引用,两个或以上的 bean
相互持有对方。比如说 beanA
引用 beanB
, beanB
引用 beanC
, beanC
引用 beanA
, 它们之间的引用关系构成一个环。
Spring 如何解决循环依赖
Spring
中的循环依赖包括
- 构造器循环依赖
setter
循环依赖
构造器的依赖
Spring
对于构造器的依赖、无法解决。只会抛出 BeanCurrentlyInCreationException
异常。
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
setter 的循环依赖
不管是 autowireByName
还是 autowireByType
都是属于这种。Spring
默认是能够解决这种循环依赖的,主要是通过 Spring
容器提前暴露刚完成构造器注入但未完成其他步骤的 bean 来完成的。而且只能解决 singleton
类型的循环依赖、对于 prototype
类型的是不支持的,因为 Spring
没有缓存这种类型的 bean
Spring 是如何解决的
其实很简单、在 Spring 获取单例流程(一) 中我们曾提及过三级缓存
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// 这个bean 正处于 创建阶段
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 并发控制
synchronized (this.singletonObjects) {
// 单例缓存是否存在
singletonObject = this.earlySingletonObjects.get(beanName);
// 是否运行获取 bean factory 创建出的 bean
if (singletonObject == null && allowEarlyReference) {
// 获取缓存中的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 将对象缓存到 earlySingletonObject中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从工厂缓冲中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
Spring
解决 setter
循环依赖的关键点就是在这里,主要是 singletonFactories
这个 Map
中
我们可以先梳理一下整体的流程
beanA --> beanB --> beanC -->beanA
以上面为例子、我们先假设它们是构造器的循环依赖
Spring
初始化完成之后、接收到一个getBean
的调用请求、请求beanA
Spring
发现三级缓存中都没有beanA
的存在、所以开始创建beanA
的流程- 将
beanA
放入到singletonsCurrentlyInCreation
集合中去、代表着beanA
正在创建中 - 兜兜转转,发现我要
new
一个beanA
的对象、我要先获得一个beanB
的对象、好、我们就进行一个getBean(beanB)
Spring
发现三级缓存中都没有beanB
的存在、所以开始创建beanB
的流程- 将
beanB
放入到singletonsCurrentlyInCreation
集合中去、代表着beanB
正在创建中 - 兜兜转转,发现我要
new
一个beanB
的对象、我要先获得一个beanC
的对象、好、我们就进行一个getBean(beanC)
Spring
发现三级缓存中都没有beanC
的存在、所以开始创建beanC
的流程- 将
beanC
放入到singletonsCurrentlyInCreation
集合中去、代表着beanC
正在创建中 - 兜兜转转,发现我要
new
一个beanC
的对象、我要先获得一个beanA
的对象、好、我们就进行一个getBean(beanA)
Spring
发现三级缓存中都没有beanA
的存在、所以开始创建beanA
的流程- 将
beanA
放入到singletonsCurrentlyInCreation
集合中去、但是在这个时候、插入到集合中失败、直接抛出异常
而假如我们是一个 setter
的循环依赖
Spring
初始化完成之后、接收到一个getBean
的调用请求、请求beanA
- 先判断三级缓存中有没有
beanA
,如果没有则往下进行 - 将
beanA
放入到singletonsCurrentlyInCreation
集合中去、代表着beanA
正在创建中 - 兜兜转转,终于创建了一个
beanA
, 但是这个时候的beanA
是一个不完整的状态、因为很多属性没有被赋值、比如说beanA
中的成员变量beanB
现在还是一个null
的状态 - 然后判断是否需要将当前创建的不完整的
beanA
加入到第三级缓存中,正常来说都是会被加入到第三级缓存中的 - 加入第三级缓存以后、进行一个属性填充,这个时候发现需要填充一个
beanB
对象 - 然后如上面那样、先看看三级缓存有没有
beanB
,如果没有则创建一个并不完整的beanB
、然后加入到第三级缓存中、然后发现需要填充一个beanC
的属性 - 然后如上面那样、先看看三级缓存有没有
beanC
,如果没有则创建一个并不完整的beanC
、然后加入到第三级缓存中、然后发现需要填充一个beanA
的属性 - 这个时候,先看看三级缓存中有没有
beanA
,发现在第三级缓冲中有不完整的beanA
、将其从第三级缓存中移除出来、放入到第二级缓存中,然后返回给beanC
用于填充属性 - 然后
beanC
的 属性填充完毕,则将其从singletonsCurrentlyInCreation
集合中移除掉,代表beanC
已经真正的创建好了 - 然后将
beanC
加入到第一级缓存中,并将其从第三级缓存中移除,并返回给beanB
,beanB
也如beanC
那样处理 beanA
也如beanB
、beanC
那样处理、加入到第一级缓存中、然后从第二级缓存中移除- 结束
其实上面的屁话又长又臭,但是流程还是非常简单的
为啥是三级缓存,二级不行吗?
/**
* Cache of singleton objects: bean name to bean instance.
* 存放的是单例 bean、对应关系是 bean Name --> bean instance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* Cache of early singleton objects: bean name to bean instance.
* 存放的早期的 bean、对应的关系 也是 beanName --> bean instance
* 与 singletonObjects 区别在于 earlySingletonObjects 中存放的bean 不一定是完整的、
* bean 在创建过程中就加入到 earlySingletonObjects 中了、所以在bean创建过程中就可以通过getBean 方法获取、
* 这个Map 也是解决循环依赖的关键所在
**/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**
* Cache of singleton factories: bean name to ObjectFactory.
* 存放的是 ObjectFactory 、可以理解为创建单例bean的factory、对应关系是 bean name --> objectFactory
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
我们来看看从第三级缓存升级到第二级缓存究竟发生了什么
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;
}
// 默认实现
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
其实只要有二级缓存也是可以的,虽然可以达到解决 setter
循环依赖的问题、但是却无法给用户提供一个扩展接口(当存在循环依赖的)。
就好比说、上面的例子、在循环依赖的关系中,当 beanA
从第三级缓存升级到第二级缓存的时候,我们可以在其升级的时候去设置一些 beanA
的属性或者做一些其他事情,我们只需要在 beanA 的类中实现 SmartInstantiationAwareBeanPostProcessor
接口即可
但是单纯只有二级缓存的话,当我们创建好一个没有完成初始化的 bean
的时候、要么就直接调用 ObjectFactory
的 getObject
方法获取经过回调的 bean
放入到第二级缓存(不管这个 bean
存不存在一个循环引用的关系链中),要么就直接放刚刚创建好的没有完成初始化的 bean
放入到第二级缓存。无论是哪种情况,都无法达到这样一个需求:当存在循环依赖的时候,我们作为用户需要对其进行一些设置或者一些其他的操作
为啥不能解决构造函数的循环依赖
如果按照解决 setter
循环依赖的流程、是否能够解决?先将一个不完整的 bean
放入到第三级缓存中,然后提供出去给其他 bean
依赖。但是呢,问题是我无法创建出这么一个不完整的 bean
在一个构造函数依赖的关系中,参数不全,再牛皮也不能把
以上是关于Spring — 循环依赖的主要内容,如果未能解决你的问题,请参考以下文章