admin管理员组

文章数量:1534215

在Spring框架中,处理循环依赖一直是一个备受关注的话题。这是因为Spring源代码中为了解决循环依赖问题,进行了大量的处理和优化。同时,循环依赖也是Spring高级面试中的必考问题,回答得好可以成为面试中的必杀技。因此,本文旨在为大家提供深入了解Spring的循环依赖及其解决方案的资料,让读者能够在日后的面试中更有把握地回答相关问题!

一、什么是循环依赖

循环依赖其实就是循环引用,也就是一个或多个以上的对象互相持有对方,最终形成闭环,形成一个无限循环的依赖关系。比如 A依赖于A本身(左图),A依赖于B,B也依赖与A(中),A依赖B,B依赖C,C又依赖A(右图)。

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情,后面我们都以A和B的相互循环依赖进行举例:

	A a = new A();
	B b = new B();

	a.b = b;
	b.a = a;

然而,Spring的循环依赖通常被单独拎出来谈论,也经常在面试中被提及。这是因为 Spring 中对象的创建和管理是由 IOC 控制的,一个对象的创建不仅仅是简单地调用 new,而是经过了一系列 Bean 的生命周期。因此,循环依赖问题也就会随之而来。当然,Spring 中存在许多场景会导致循环依赖,有些场景 Spring 能够解决,而有些场景则需要程序员手动解决。

要深刻理解 Spring 中的循环依赖问题,首先需要理解 Spring 中 Bean 的生命周期。

二、Bean的生命周期

Spring Bean的生命周期来说,可以分为四个主要阶段:实例化、属性复制、初始化、销毁,其中经过初始化以后这个bean就被创建完成可以供使用了。

具体的步骤:

  • 实例化:实例化一个 Bean 对象
  • 属性赋值:为 Bean 设置相关属性和依赖注入
  • 初始化:初始化的阶段的步骤比较多,5和6 步是进行真正的初始化,而第 3和4 步为在初始化前执行,第 7 步在初始化后执行,如果原始对象中的某个方法被 AOP 了,那么这一步需要根据原始对象生成一个代理对象,最终生成的代理对象放入单例池,Bean 就可以被使用了。
  • 销毁:第 8~10 步,第 8 步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第 9、10 步真正销毁 Bean 时再执行相应的方法

我们可以发现,在第2步中,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?

还是以A和B两个类相互依赖来举例子,A 类中存在一个 B 类的 b 属性,所以当 A 类生成了一个原始对象之后,就需要去给 b 属性去赋值(依赖注入),此时就会根据 b 属性的类型和属性名去 BeanFactory 中去获取 B 类所对应的单例bean。如果此时 BeanFactory 中存在 B 对应的 Bean,那么直接拿来赋值给 b 属性就好了;但是如果此时 BeanFactory 中不存在 B 对应的 Bean,则需要生成一个 B 对应的 Bean,然后赋值给 b属性。

问题的关键点就在于第二种情况,此时 B 类在 BeanFactory 中还没有生成对应的 Bean,那么就需要去生成,就会经过 B 的 Bean 的生命周期。那么在创建 B 类的 Bean 的过程中,如果 B 类中还存在一个 A 类的 a 属性,那么在创建 B 的 Bean 的过程中就需要 A 类对应的 Bean。但是,B 类 Bean创建完成的条件是 A 类 Bean 在创建过程中进行依赖注入,所以这里就出现了循环依赖

在Spring中,是通过三级缓存来解决循环依赖问题的,那么什么是三级缓存?

三、三级缓存

Spring的三级缓存是指在使用Spring框架进行Bean的创建和管理时,Spring在其内部维护了三级缓存,用于提高Bean的创建和获取效率。这三级缓存分别是singletonObjects、earlySingletonObjects和singletonFactories:singletonObjects用于缓存完全初始化后的单例Bean实例,earlySingletonObjects用于缓存尚未完全初始化的单例Bean实例,而singletonFactories则用于缓存Bean工厂对象,即可以生成Bean实例的工厂方法。

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

一级缓存

singletonObjects:key -> beanName,value -> 完整bean对象

一级缓存的是经历了完整的Bean生命周期的Bean。

二级缓存

earlySingletonObjects:key -> beanName,value -> 不完整(属性信息未填充完毕)bean对象

二级缓存用于存放未完成Bean生命周期的半成品Bean。在出现循环依赖时,这些Bean已经被提前放入二级缓存。如果需要进行AOP处理,则其代理对象也已经被放入缓存。

三级缓存

singletonFactories:key -> beanName,value -> bean工厂对象

缓存的是ObjectFactory , 也就是一个Lambda表达式 , 这就一个方法getObject,返回bean对象或bean代理对象,用于解决被代理增强的循环依赖

四、循环依赖的解决(源码分析)

我们已经知道,问题的关键在于A在创建的时候需要将B注入到A中,而注入B需要先创建B,创建B的时候发现需要将A注入到B中,产生了先有鸡还有现有蛋的问题。Spring解决这个问题的思想就是在实例化过程中,提前办成品bean放入缓存,在依赖注入的时候允许将半成品进行注入:
实例化A -> a的半成品写入缓存 -> 属性注入B -> B还没有实例化,需要先进行实例化B -> 实例化B -> 没有A,但是有A的半成品 -> 注入A的半成品 -> 实例化B成功 -> 实例化A成功

上面就是核心思想,下面将会结合源码进行具体分析其中的原理。

4.1 普通循环依赖

还是使用之前的例子来举例:

@Component
public class A {
    // A中注入了B
    @Autowired

本文标签: 是怎样带你万字长文Spring