1 循环依赖方式

1.1 构造参数循环依赖

public class A{
private B b;
public void A(B b){
this.b=b;
}
}

public class B{
private A a;
public void B(A a){
this.a=a;
}
}
<bean id="a" class="...A">
<constructor-arg index="0" ref="b"></constructor-arg>
</bean>

<bean id="b" class="...B">
<constructor-arg index="0" ref="a"></constructor-arg>
</bean>

Spring先创建(singleton) A,A依赖B,将A放进当前创建Bean池中;然后创建B,B依赖A,将B放进当前创建Bean池中,然后发现A已经在池中。

1.2 setter方式(单例,singleton),默认方式

image.png

Spring是先将Bean对象实例化之后再设置对象属性的

1.3 setter方式(原型,prototype)

scope=“prototype”,每次请求都会创建一个实例对象

2 循环依赖(circular reference)

在 Spring 中,循环依赖是指两个或多个 Bean 之间互相依赖的情况,导致 Bean 的创建发生循环,从而无法完成依赖注入的过程。这种情况可能会导致应用程序启动失败或者出现不可预测的行为。

Spring 框架提供了一些解决循环依赖的机制:

  1. 提前曝光(Early Exposure): Spring 容器在创建 Bean 的过程中,会提前暴露正在创建的 Bean 实例,以解决循环依赖的问题。当一个 Bean A 依赖另一个 Bean B,而 Bean B 又依赖 Bean A 时,Spring 在创建 Bean A 的过程中,会提前暴露一个代理对象,用于处理 Bean B 对 Bean A 的依赖。这样,Bean A 可以在被完全创建之前,通过代理对象来访问 Bean B。这种方式需要使用 CGLIB 来创建代理对象。

  2. 构造函数注入: 使用构造函数注入来解决循环依赖问题。Spring 容器在创建 Bean 的过程中,会首先将依赖项通过构造函数传递进去,从而避免了循环依赖的问题。这种方式需要谨慎使用,因为构造函数注入会将循环依赖暴露在类的构造函数中,可能导致代码不够清晰。

  3. Setter 方法注入: 使用 Setter 方法注入来解决循环依赖问题。与构造函数注入类似,通过将依赖项通过 Setter 方法注入,可以避免循环依赖的问题。与构造函数注入相比,Setter 方法注入更加灵活,可以在 Bean 创建完成后再进行依赖注入,但也需要注意循环依赖可能带来的问题。

虽然 Spring 提供了这些解决循环依赖的机制,但是在设计应用程序时,尽量避免出现循环依赖是更好的选择。循环依赖会导致代码的复杂性增加,降低程序的可维护性和可读性。

3 三级缓存解决循环依赖

  • 一级缓存(singletonObjects):存放最终形态的 Bean(已经实例化、属性填充、初始化),单例池,为“Spring 的单例属性”⽽⽣。一般情况我们获取 Bean 都是从这里获取的,但是并不是所有的 Bean 都在单例池里面,例如原型 Bean 就不在里面。

  • 二级缓存(earlySingletonObjects):存放过渡 Bean(半成品,尚未属性填充),也就是三级缓存中ObjectFactory产生的对象,与三级缓存配合使用的,可以防止 AOP 的情况下,每次调用ObjectFactory#getObject()都是会产生新的代理对象的。

  • 三级缓存(singletonFactories):存放ObjectFactoryObjectFactorygetObject()方法(最终调用的是getEarlyBeanReference()方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。三级缓存只会对单例 Bean 生效。

3.1 Spring 创建 Bean 的流程:

  • 先去 一级缓存 singletonObjects 中获取,存在就返回;

  • 如果不存在或者对象正在创建中,于是去 二级缓存 earlySingletonObjects 中获取;

  • 如果还没有获取到,就去 三级缓存 singletonFactories 中获取,通过执行 ObjectFacotrygetObject() 就可以获取该对象,获取成功之后,从三级缓存移除,并将该对象加入到二级缓存中。

[!note]
只用两级缓存够吗? 在没有 AOP 的情况下,确实可以只使用一级和三级缓存来解决循环依赖问题。但是,当涉及到 AOP 时,二级缓存就显得非常重要了,因为它确保了即使在 Bean 的创建过程中有多次对早期引用的请求,也始终只返回同一个代理对象,从而避免了同一个 Bean 有多个代理对象的问题。

4 Spring源码

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

/** Maximum number of suppressed exceptions to preserve. */
private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;


/** 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);

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从 singletonObjects 获取实例,singletonObjects 中缓存的实例都是完全实例化好的 bean,可以直接使用
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 加入到三级缓存,暴漏早期对象用于解决循环依赖
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}

return singletonObject != NULL_OBJECT ? singletonObject : null;
}


}
  • singletonObjects:用于存放初始化好的 bean 实例,存放完整对象。

  • earlySingletonObjects,用于存放初始化中的 bean,来解决循环依赖。存放半成品对象,属性还未赋值的对象。

  • singletonFactories:用于存放 bean 工厂,bean 工厂所生成的 bean 还没有完成初始化 bean。存放的是 ObjectFactory<?> 类型的 lambda 表达式,就是这用于处理 AOP 循环依赖的。

相互引用的bean,A依赖B,把A原始对象包装成SingletonFactory 放入三级缓存
B依赖A,B依赖的A是从singletonFactories获取bean工厂调用getObject方法生产bean放入earlySingletonObjects。

5 参考

  1. 面试必问:Spring 循环依赖的三种方式 - 知乎 (zhihu.com)

  2. 面经手册 · 第31篇《Spring Bean IOC、AOP 循环依赖解读》 | 小傅哥 bugstack 虫洞栈

  3. Spring 循环依赖那些事儿(含Spring详细流程图) - 知乎 (zhihu.com)

  4. 什么是循环依赖以及解决方式-CSDN博客

  5. Spring Bean 循环依赖 - spring 中文网 (springdoc.cn)(*****)

  6. Spring源码最难问题《当Spring AOP遇上循环依赖》_循环依赖aop在那个阶段-CSDN博客

  7. Spring 中的控制反转(IoC)和依赖注入(DI) - spring 中文网 (springdoc.cn)