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是怎样解决循环依赖的 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1726874078a1088149.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论