【Spring面试题】循环依赖如何解决?

循环依赖是指两个类互相依赖对方的属性,Spring针对setter注入的单例Bean提供了循环依赖解决方案,通过三级缓存机制来处理。构造器注入的循环依赖Spring无法解决,但可以通过setter注入、@Lazy、@PostConstruct以及实现特定接口来规避。文章深入探讨了Spring如何处理这两种情况的循环依赖,并解释了为何选择三级缓存而不是二级缓存。

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

1.什么是循环依赖

循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环。

2.循环依赖的两种情况及其解决方案

2.1 构造器的循环依赖

这种依赖 spring 本身是处理不了的

在这种情况下 Spring 将在加载上下文时引发 BeanCurrentlyInCreationException 异常,因为 Spring 无法决定应该先创建哪个 bean,因为它们彼此相互依赖。

当我们使用构造方法进行注入时,才会遇到这种情况,因为它在上下文加载时就被要求注入。setter 和 field 方法注入则不会发生循环依赖,因为它们不会在创建 Bean 时就注入依赖,而是在被需要时才注入。

解决方案:

  • 采用 setter 和 field 方法注入
  • 使用懒加载 @Lazy
  • 使用 @PostConstruct
  • 实现 ApplicationContextAware 和 InitializingBean 接口

2.2 单例模式下的 setter 循环依赖

Spring 解决循环依赖的机制是根据 Spring 框架内定义的三级缓存来实现的,三级缓存解决了 Bean 之间的循环依赖问题。

从源码中来看看 Spring 中 Bean 工厂是怎么获取 Bean 的(AbstractBeanFactory中):

一级一级向下寻找,可以找到 DefaultSingletonBeanRegistry 类中的三级缓存,其实就是三个 Map 集合类:

  • 第一级缓存【singletonObjects】:里面存放的是实例化好和完成初始化的单例对象
  • 第二级缓存【earlySingletonObjects】:里面存放的是提前曝光的单例对象,这个 Bean 实例化了,但是还没有初始化
  • 第三级缓存【singletonFactories】:里面存放的是要被实例化的对象的对象工厂。

所以当一个 Bean 调用构造函数进行实例化后,即使这时候属性还未填充,依然可以通过三级缓存向外暴露依赖的引用值(所以循环依赖问题的解决也是基于 Java 的引用传递),这也说明了另外一点,基于构造器的注入,如果有循环依赖,Spring是不能够解决的。

还有一点,Spring 默认的 Bean Scope 是单例的,而三级缓存中都包含 singleton,可见是对于单例 Bean 之间的循环依赖的解决。

其实二级缓存也是可以解决循环依赖的。为什么 Spring 不选择二级缓存?

如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理,而 Spring 的设计原则是在 Bean 初始化完成之后才为其创建代理。所以,Spring 选择了三级缓存。但是因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的架狗师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值