Spring如何解决循环依赖

文章详细阐述了Spring如何处理单例模式下的setter循环依赖问题,通过介绍Bean的生命周期和三级缓存机制(完全初始化Bean、早期单例对象、Bean工厂对象)来说明Spring如何在实例化和属性填充过程中避免无限递归。Spring的三级缓存设计是为了处理AOP代理对象的生成,确保在循环依赖时能正确创建和注入代理对象。

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

今天面试了同程旅行,面试官问到了这个问题,所以今天来总结学习一下 Spring是如何解决循环依赖问题?

前言

Spring的依赖注入分为 setter注入构造器注入
这里说的解决循环依赖主要指的是:单例模式下的setter循环依赖

如果是:构造器的循环依赖非单例循环依赖。 Spring都是解决不了的

Spring的生命周期

讲循环依赖的问题,需要先说说Spring生命周期这个问题,先看看在Spring中,一个Bean从创建到销毁经历过哪些过程?

Spring中的bean的生命周期主要包含四个阶段:实例化Bean --> Bean属性填充 --> 初始化Bean -->销毁Bean

什么是循环依赖

我们定义这样两个类A、B,A中有一个属性引用的B,B中有一个属性是引用的A

参考上面Bean的生命周期,就能得出:在实例化A的时候,去赋值属性B,就去找B,B这个时候又需要实例化赋值属性A的时候,又去找A。接着A又找B,就形成了循环依赖。

@Component
public class A{
  @Autowired
  B b;
}

@Component
public class B{
  @Autowired
  A a;
}

Spring的三级缓存

// 1级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 2级缓存:存放实例化(尚未填充属性)+代理(如果有代理)后的单例bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

//3级缓存,存放bean工厂对象,用于解决循环依赖
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

假设只有一级缓存

如果A在实例化过后,不进行属性值的填充,就把它丢到一级缓存里面行吗?其实这样缓存里面的A也不是一个真正的A,它缺胳膊少腿,用它的时候回报一个空指针异常,而且我们的一级缓存规定是完全初始化好的bean才放在一级缓存立面,给我们的程序后续进行使用

假设只有二级缓存

我们把一级缓存叫 Map1,二级缓存叫Map2

首先还是从实例化A开始,我们对A实例化过后,还没有进行属性填充的时候,就把A对象的引用放在Map2备用,然后进行属性填充,A去填充B的时候,发现B没有实例化;于是等B同样实例化过后,B也把自己的引用放在Map2中,B开始进行属性填充,发现Map1中没有A,但在Map2中找到了A,是自己装填完整。这个时候B把完整的自己放在了Map1,随手把Map2中的半成品删除。
再回到A的阶段,A中发现Map1中有了B,那么A也可以装填完整,于是最终A、B都完成了自己的创建。

文字有点儿扭成一坨不容易看懂,我画个流程图,大家凑合看

在这里插入图片描述
按照以上所述,二级缓存已经完美解决了循环依赖的问题,为什么Spring还需要引入三级缓存来做呢?

主要原因是因为:Spring的Aop产生的代理对象

Spring的代理对象产生阶段是在填充属性后才进行,原理是通过后置处理器BeanPostProcssor来实现

如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理

所以Spring选择使用三级缓存,因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。

Spring的三级缓存

首先我们还是进行实例化A对象,我们将A的ObjectFactory对象放入Map3中,同样进行属性填充,这个时候发现需要属性B,这个时候B还没有创建。
接着去创建B,实例化B的过程中,一样的我们先将B的ObjectFactory放到Map3中,继续执行B的属性填充,去寻找A对象。此时Map1和Map2中都没有找到A对象,但在Map3中发现了有A的 ObjectFactory对象,那么我们就可以通过这个 ObjectFactory对象获取到A的早期对象,并且把A早期对象放到Map2中国,同时删除Map3中的A,我们把Map2中的早期对象A给了B,让B对象进行属性装填,接着B完整了,就把B放入到一级缓存当中,再把Map3中B的 ObjectFactory删除。
B创建完成,A继续执行b属性的填充就可以拿到Map1中拿到B对象,这样A也完整了。
最后把A对象放入到Map1单重,删除Map2中的A,于是我们的循环依赖的解决了

画一张流程图试着理解一下:

在这里插入图片描述

总结

面试官:”Spring是如何解决的循环依赖?“

答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。

至此,循环依赖结束!

面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值