循环依赖
循环依赖是指两个或多个 Bean 之间相互依赖形成的循环引用关系。在 Spring 中,循环依赖通常出现在以下场景中:
-
构造器注入循环依赖: 当两个或多个 Bean 在它们的构造器中相互依赖时,就会出现构造器注入的循环依赖。例如,BeanA 的构造器参数依赖于 BeanB,而 BeanB 的构造器参数又依赖于 BeanA。
-
setter 方法注入循环依赖: 当两个或多个 Bean 在它们的 setter 方法中相互依赖时,就会出现 setter 方法注入的循环依赖。例如,BeanA 的某个属性依赖于 BeanB,而 BeanB 的某个属性又依赖于 BeanA。
循环依赖图示
示例:假如A中有B,B中有A,则进行创建时,会发生什么?
如果没有三级缓存,将永远无法完成对象创建
- 这里已经形成了完美的闭环,要想破坏闭环,势必要挑选其中一条路径进行破坏,那么破坏哪一条路径呢?
破坏最后一条路径
- 破坏的前提是,在创建过程中,
实例化和初始化赋值是分开的
。- 在此前提下,引入了两个概念:
成品
、半成品
- 成品:即完成了实例化和初始化
- 半成品:即只完成了实例化,而未完成初始化
过程解析
- 因此,当在进行最后一步时,此时,对象AB都已完成实例化,即,已经有了对象AB,只不过为半成品
- 在进行最后一步,即初始化B对象中的A属性时,进行判断A对象是否存在?此时A对象已经存在,只不过是半成品。
- 完成赋值后,B对象完成创建,B转化为成品对象
- 对A对象完成B赋值后,A对象转化为成品对象
- 最终完成了AB的创建
在详细描述整个过程之前,先引入
三级缓存
的概念
三级缓存
1、三级缓存结构示例
在 Spring 中,Bean 的创建过程涉及到三级缓存。这三级缓存分别是 singletonObjects、earlySingletonObjects 和 singletonFactories。
- 一级缓存:
singletonObjects
用于存储已经完成初始化的 Bean 实例 - 二级缓存:
earlySingletonObjects
用于存储尚未完成初始化的 Bean 实例 - 三级缓存:
singletonFactories
用于存储创建 Bean 的工厂对象
// 一级缓存:singletonObjects
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:earlySingletonObjects
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:singletonFactories
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2、getSingleton()方法
在
getSingleton()
方法中,首先会尝试从一级缓存singletonObjects
中获取 Bean 实例,如果找不到且 Bean 正在创建中,则会从二级缓存earlySingletonObjects
中获取 Bean 实例,如果仍然找不到且允许早期引用,则会从三级缓存singletonFactories
中获取 Bean 实例的工厂对象,并使用工厂对象创建 Bean 实例,然后将创建好的 Bean 实例放入二级缓存earlySingletonObjects
中。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 快速检查一级缓存中是否有单例对象,避免完全锁定
Object singletonObject = this.singletonObjects.get(beanName);
// 如果一级缓存中没有,并且该bean正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 从二级缓存(earlySingletonObjects)中获取单例对象,之所以称之为早期单例对象,
// 是因为从这个缓存中获取的对象是通过提前曝光的ObjectFactory创建出来的,还未进行属性填充等操作
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) {
// 如果都没有,从三级缓存(singletonFactories)中获取单例工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 使用工厂方法创建单例对象
singletonObject = singletonFactory.getObject();
// 将创建的单例对象放入二级缓存,供后续使用
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中移除单例工厂
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
于是二者结合,就有了以下内容
-
singletonObjects
:- 这是最常见的单例对象缓存。当容器创建 Bean 时,会首先尝试从 singletonObjects 缓存中获取已经创建好的 Bean 实例。
- 如果能够从 singletonObjects 中获取到 Bean 实例,就直接返回该实例。
- 如果 singletonObjects 缓存中不存在 Bean 实例,则继续后续的创建流程。
-
earlySingletonObjects
:- 这个缓存用于存储尚未完成初始化的早期单例对象。
- 在创建 Bean 的过程中,如果发现 Bean 的初始化依赖其他 Bean,而这些依赖的 Bean 正好是单例的,则会暂时将正在创建的 Bean 实例放入 earlySingletonObjects 缓存中,以便解决循环依赖的问题。
- 等到 Bean 的创建完成后,会将其移动到 singletonObjects 缓存中。
-
singletonFactories
:- 这个缓存用于存储创建 Bean 的工厂对象(ObjectFactory)。
- 当 Bean 的创建过程中需要解决循环依赖时,会将创建 Bean 的工厂对象放入 singletonFactories 缓存中。
- 当需要获取正在创建的 Bean 的依赖时,会从 singletonFactories 缓存中获取对应的工厂对象,然后通过工厂对象创建 Bean 的代理对象,并将代理对象放入 earlySingletonObjects 缓存中,以便解决循环依赖的问题。
完整的过程分析
下面,为了可以通透的学会整个知识点,我们从头开始,捋一遍
1、在refresh()
中,有个方法finishBeanFactoryInitialization()
不清楚refresh的可以去我的另一篇【Spring启动过程中核心方法refresh()】
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// 初始化剩余的单例(非懒加载)Bean。
beanFactory.preInstantiateSingletons();
}
2、其中有个方法叫做beanFactory.preInstantiateSingletons()
public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) {
logger.trace("Pre-instantiating singletons in " + this);
}
// 获取所有 BeanDefinition 的名称
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 循环所有的 BeanDefinition 名称,实例化所有非懒加载的单例 Bean。
for (String beanName : beanNames) {
// 获取对应bean的BeanDefinition
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 条件判断,抽象,单例,非懒加载
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
//判断是否实现了FactoryBean接口
if (isFactoryBean(beanName)) {
//根据&+beanName来获取具体的对象
FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit, getAccessControlContext());
} else {
isEagerInit = (factory instanceof SmartFactoryBean && ((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
//如果beanName对应的bean不是FactoryBean,只是普通的bean,通过beanName获取bean实例
getBean(beanName);
}
} else {
// 如果beanName对应的bean不是FactoryBean,只是普通的bean,通过beanName获取bean实例
// 这里就是循环依赖/三级缓存开始的地方
getBean(beanName);
}
}
}
// 遍历 Bean 工厂中的所有 bean,并初始化那些实现了 SmartInitializingSingleton 接口的单例 bean
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
smartSingleton.afterSingletonsInstantiated();
return null;
}, getAccessControlContext());
} else {
smartSingleton.afterSingletonsInstantiated();
}
}
}
}
3、其中有个方法,叫 getBean(beanName);
于是就有了一条链路
// 总结来说,细节不需要关注,只关注重点
// getBean() 方法调用 doGetBean() 来实际获取或创建 Bean
// doGetBean() 中的getSingleton(beanName)方法,会检查各级缓存,如果未找到则调用 createBean() 来创建新的 Bean 实例
// createBean() 方法最终调用 doCreateBean() 来创建实际的 Bean 实例
// doCreateBean() 会实例化 Bean、提前曝光 Bean、填充 Bean 属性并初始化 Bean。
// doCreateBean() 中,如果允许循环依赖且需要提前曝光 Bean,会将创建的对象放入三级缓存
// populateBean() 方法则负责将属性值注入到 Bean 实例中
// 也就是populateBean()这里,将是循环依赖环的正式出现。如果属性为一个其他对象,则会调用getBean()去获取对应对象
getBean() -> doGetBean() -> createBean() -> doCreateBean() -> populateBean()
先不管具体每个方法的细节,只需要知道这个链路即可。
4、完成过程
于是,根据上面我们得到的内容:
- getBean() -> doGetBean() -> createBean() -> doCreateBean() -> populateBean()
- 一级缓存:
singletonObjects
用于存储已经完成初始化的 Bean 实例- 二级缓存:
earlySingletonObjects
用于存储尚未完成初始化的 Bean 实例- 三级缓存:
singletonFactories
用于存储创建 Bean 的工厂对象,也可以说是lamda表达式- 在
getSingleton()
方法中,首先会尝试从一级缓存singletonObjects
中获取 Bean 实例,如果找不到且 Bean 正在创建中,则会从二级缓存earlySingletonObjects
中获取 Bean 实例,如果仍然找不到且允许早期引用,则会从三级缓存singletonFactories
中获取 Bean 实例的工厂对象,并使用工厂对象创建 Bean 实例,然后将创建好的 Bean 实例放入二级缓存earlySingletonObjects
中。就有了以下过程:
以A中包含B属性,B中包含A属性为例
- 在
Bean A
进行创建的过程中,执行getBean(A)链路
。执行doGetBean()
中的getSingleton()
获取Bean A
,显然此时三个缓存都是空的
,因此需要继续执行createBean()
创建Bean A
- 随后,在执行完创建创建
Bean A
的链路中的doCreateBean()
之后,在三级缓存singletonFactories
中,就存在了Bean A的工厂对象
- 继续执行创建
Bean A
的链路,执行populateBean()
时,由于需要对属性B进行填充
,于是尝试获取Bean B
进行填充,于是尝试getBean(B)
- 这个地方,我们暂时称为
节点1
注意,循环开始了
- 此时进入
Bean B
的getBean(B)链路
,执行doGetBean()
中的getSingleton()
获取Bean B
,因为Bean B还不存在
,所以需要继续执行createBean()
创建Bean B
- 于是在
Bean B
创建的过程中,执行完doCreateBean()
之后,在三级缓存singletonFactories
中,就存在了Bean B的工厂对象
- 继续执行创建
Bean B
的链路,继续执行populateBean()
,需要对属性A进行填充
,于是尝试getBean(A)
- 这个地方,我们暂时称为
节点2
注意,通过三级缓存破解循环依赖开始了
- 此时,又一次的执行
getBean(A)链路
。 - 这一次,在执行
doGetBean()
中的getSingleton()
获取Bean A
时,将会发现,在三级缓存singletonFactories
中,存在Bean A的工厂对象
。 - 于是,通过
getSingleton()
中的如下操作,将三级缓存singletonFactories
中存在的Bean A的工厂对象
转变为二级缓存earlySingletonObjects
中的半成品Bean A
。(在这个过程中,还涉及到了代理对象的生成,找空再开一章学习一下)// 从三级缓存(singletonFactories)中获取单例工厂 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 使用工厂方法创建单例对象 singletonObject = singletonFactory.getObject(); // 将创建的单例对象放入二级缓存,供后续使用 this.earlySingletonObjects.put(beanName, singletonObject); // 从三级缓存中移除单例工厂 this.singletonFactories.remove(beanName); }
- 此时,就获取到了
Bean A
,跳转返回到了节点2
,于是Bean B
的属性A
也就完成了赋值。Bean B
就完成了创建的过程 - 同时,因为是新生成的单例对象,于是将
Bean B
加入一级缓存singletonObjects
中 - 注意,此时完成了
Bean B
的创建,那么我们是为什么开始创建Bean B
的呢?节点1
!!! - 因此,此时完成
Bean B
的创建后,跳转返回到了节点1
- 于是对
Bean A
中的B属性进行赋值。Bean A
就完成了创建的过程 - 同时,因为是新生成的单例对象,于是将
Bean A
加入一级缓存singletonObjects
中
至此,Bean A就彻底完成了创建。记住,此时Bean B的创建,在初始化层面还未开始,即Bean B的创建,是Bean A创建的附带产物
因此,一会还需要创建一遍Bean B
。只不过,在最初进行getBean(B)
时,一级缓存中已经存在了,直接就可返回Bean B
为什么需要三个缓存呢?两个为什么不行
-
如果只有一个map结构,能解决循环依赖问题吗?
- 理论上可行,实际上没人这么干,使用两个map的意义在于将成本对象和半成品对象进行区分,半成品对象是不能直接暴露给外部对象使用的,可以设置标志位来标识成品还是半成但是操作起来比较麻烦,所以直接用两个map即可品,
-
如果只有两个map结构,能解决循环依赖问题吗?
- 可以解决,但是有前提:没有代理对象的时候,当不使用aop的时候,两个缓存map就可以解决循环依赖问题
- 如果使用了aop,就会报下面的错
- Exception in thread "main"org.springframework.beans.factory.BeanCurrentlyinCreationException:
- Error creating bean with name ‘a’: Bean with name ‘a’ has been injected into otherbeans fbl in its raw version as part of a circular reference, but has eventually beenwrapped.
- This means that said other beans do not use the final version of the bean.
- This is often the result of over-eager type matching - consider usinggetBeanNamesForType’ with the ‘allowEagerlnit’ flag turned off, for example.
为什么使用三级缓存之后就可以解决带aop的循环引用?
-
1、一个容器中,能包含同名的两个对象吗?
- 不能
-
2、对象创建过程中,原始的对象有没有可能需要生成代理对象?
- 有
-
3、如果创建出了代理对象,那么程序在调用的时候到底使用原始对象还是代理对象?
- 应该用代理对象,但是程序是死的,是提前写好的,他怎么知道要选择代理对象呢?所以,当出现代理对象的时候,要使用代理对象替换掉原始对象
-
代理对象的创建实在初始化过程的扩展阶段,而属性的赋值是在生成代理对象之前执行的那怎么完成替换呢?
- 需要在属性赋值的时候判断是否需要生成代理对象
-
那为什么非要使用lambda表达式的机制来完成呢?
- 对象在什么时候被暴露出去或者被其他对象引用是没办法提前确定好的,所以只有在被调用的那刻才可以进行原始对象还是代理对象的判断,使用lambda表达式类似于一种回调机制,不暴露的时候不需要调用执行,当需要被调用的时候,才真正的执行lambda表达式,来判断返回的到底是代理对象还是原始对象。对应到上面的步骤中,是在哪一步呢?