对Spring循环依赖的一些理解

文章介绍了Spring框架在处理循环依赖时的一级缓存和二级缓存机制,并解释了为何需要三级缓存来解决AOP代理问题。通过实例展示了循环依赖的产生及解决过程,强调了三级缓存如何在保证AOP功能的同时避免过早暴露原始对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是循环依赖

spring相互依赖怎么解决 被问到Spring循环依赖怎么解决 秀给面试官看 内附图解...

 类A有个字段需要注入类B,类B有个字段需要注入类C,类C有个字段需要注入类A,它们之间的依赖关系形成了一个循环。

Spring初始化完一个对象之后会把实例放入单例池(singletonObjects)中,也就是一级缓存。

我们看看一级缓存下循环依赖造成的结果。

1、new A(),发现需要B对象

2、去singletonObjects找B对象,没有找到

3、new B(),发现需要C对象

4、去singletonObjects找C对象,没有找到

5、new C(),发现需要A对象

6、去singletonObjects找A对象,没有找到

然后回到第一步,发现这个是死循环,最后会因为堆栈溢出而结束运行,所以一级缓存无法解决循环依赖问题。

循环依赖怎么解决

再新增一级缓存,把对象提前暴露出来(对象实例化但未初始化),新增一级的缓存叫earlySingletonObjects。

1、new A(),放入earlySingletonObjects, 发现需要B对象

2、去singletonObjects找B对象,没有找到

3、去earlySingletonObjects找B对象,没有找到

4、new B(),放入earlySingletonObjects,发现需要C对象

5、去singletonObjects找C对象,没有找到

6、去earlySingletonObjects找C对象,没有找到

7、new C(),放入earlySingletonObjects,发现需要A对象

8、去singletonObjects找A对象,没有找到

9、去earlySingletonObjects找A对象,找到了

10、给C对象的字段注入A对象,C对象初始化完成,把C对象放入singletonObjects

11、返回B对象的创建中,在singletonObjects找到C对象

12、给B对象的字段注入C对象,B对象初始化完成,把B对象放入singletonObjects

13、返回A对象的创建中,在singletonObjects找到B对象

14、给A对象的字段注入B对象,A对象初始化完成,把A对象放入singletonObjects

15、END

二级缓存就可以解决循坏依赖的问题了。

Spring为什么使用三级缓存解决循环依赖

但Spring为什么还用三级缓存去解决呢?

因为二级缓存虽然解决了循环依赖,但是还有AOP的问题没有解决。

如果一个Bean需要被代理,那么它在初始化完成之后原始Bean会被代理Bean替换掉放入IOC容器,其他Bean注入的也是代理对象,如果使用上述二级缓存的流程,那么其他对象注入的都是原始对象,AOP就失效了。

如果使用上述的二级缓存,还有一个方法可以解决AOP失效,就是提前AOP,这样earlySingletonObjects放的就是代理对象了。但是有个问题,你在实例化A的时候,你根本不知道A产生了循环依赖,你只有到了实例化C,为C注入属性的时候才能知道A产生了循环依赖。如果我们可以提前知道A没有循环依赖,我们就可以不用提前AOP(没有循环依赖也就是不用提前暴露bean,其他bean也就不会提前拿到并注入),A走正常流程就可以了。现在问题是我们不能提前知道循环依赖,所以只能无差别的对Bean提前进行AOP。

这是一个解决方案,但不是一个好方案,因为AOP的流程被提前了,相当于Bean的生命周期改变了,Bean的生命周期在整个Spring框架中是核心、底层的东西。这个方案相当于架构为问题让步、妥协,我们知道架构这东西一旦定下来就很少再改了,除非迫不得已,否则底层的东西不会向问题让步,最不济也是选一个比较low一点、折中一点的解决方案。

这个问题还不至于底层让步,现在关键在于如果我们能知道一个bean出现循环依赖了,我们就提前AOP,没有出现循环依赖的,正常走流程,这样不至于所以bean都提前AOP,没有对原先的架构造成破坏,也做了一点妥协,部分bean需要提前AOP,是一个比较折中的方案。

所以加了第三级缓存singletonFactories,作用就是解决AOP问题,在earlySingletonObjects之前加一层缓冲,因为我也不知道现在这个bean是不是循环依赖了,所以不能过早暴露出来。

对应上述例子,

实例化A,放入singletonFactories(不知道A是循环依赖对象),...

...

...

...

实例化C,需要注入A

singletonObjects找A没有

earlySingletonObjects找A没有

singletonFactories找A,找到了

调用singletonFactories的get方法,此时在这里就可以知道A是否循环依赖了,循环依赖了就提前AOP返回代理对象,没有循环依赖就返回原始对象。

remove singletonFactories的A

add earlySingletonObjects A

...

...

...

这样三级缓存的条件下,解决了循环依赖(部分场景)+AOP的问题。

总的来说,二级缓存解决了循环依赖问题,同时也可以解决AOP问题,但是需要所以Bean都无条件的提前AOP,这样做破环了原先的Bean生命周期流程,实现起来不够优雅。


所以引入了三级缓存,不提前暴露原始对象,而是暴露一个lambda表达式,当被调用的时候,在lambda里面可以知道是否出现的循环依赖,对象是否需要AOP,通过这些判断,返回原始对象或者提前为Bean执行AOP并返回代理对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值