Skip to Content
面试题库Spring Core[面试题] Spring的循环依赖问题是什么?如何解决循环依赖问题?

[面试题] Spring的循环依赖问题是什么?如何解决循环依赖问题?

📖 版块 A:知识讲解版 (温故知新)

  • 什么是循环依赖:指两个或多个 Bean 之间互相持有对方的引用,形成了一个闭环。例如:Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。

  • Spring 解决的范围

    1. 只支持 单例(Singleton) 作用域的 Bean。原型(Prototype)作用域的循环依赖无法解决,会抛出异常。
    2. 只支持 Setter 注入Field 属性注入。构造器注入产生的循环依赖无法自动解决。
  • 核心机制:三级缓存

    1. 一级缓存 (singletonObjects):存放已经完全初始化好的单例 Bean。
    2. 二级缓存 (earlySingletonObjects):存放早期的 Bean(半成品,已实例化但未填充属性),用于解决循环依赖时的提前引用。
    3. 三级缓存 (singletonFactories):存放 ObjectFactory(工厂对象),用于生成 Bean 的代理对象。
  • 解决逻辑:通过“提前暴露”未完全初始化的对象引用,利用三级缓存来打破死锁循环。

🎤 版块 B:面试实战版 (高手逐字稿)

Q:Spring 的循环依赖问题是什么?如何解决循环依赖问题?

💬 高手逐字稿

关于 Spring 的循环依赖,简单来说就是 A 依赖 B,B 又反过来依赖 A。在 Spring 容器初始化时,如果没有处理机制,就会陷入死循环导致程序崩溃。

Spring 解决这个问题的核心思想是“提前暴露”,并为此设计了三级缓存机制:

第一级缓存是 singletonObjects,存的是完全体,也就是已经走完整个生命周期的 Bean。第二级缓存是 earlySingletonObjects,存的是半成品,即实例化了但还没属性填充的 Bean,用来保证在循环依赖时,大家拿到的都是同一个引用的副本。第三级缓存是 singletonFactories,它最关键,存的是一个工厂对象 ObjectFactory

我详细梳理一下流程:当 Spring 创建 A 时,先通过构造函数实例化 A,然后把 A 包装成一个工厂放入三级缓存中。接着 A 发现自己依赖 B,就开始创建 B。B 实例化后也放入三级缓存,并发现自己依赖 A。这时候,B 会去一级、二级缓存找 A,没找到,最后在三级缓存里找到了 A 的工厂。B 通过工厂拿到 A 的引用(如果 A 有 AOP 增强,此时拿到的就是代理对象),并将 A 存入二级缓存。B 完成初始化后,把自己放入一级缓存。最后 A 拿到 B,也完成初始化。

这里有一个很经典的细节:为什么一定要用三级缓存,二级行不行? 如果我们的 Bean 只是简单的 POJO,确实二级缓存就够了。但 Spring 必须要考虑 AOP 代理。根据 Spring 的生命周期,代理对象的创建通常是在初始化之后的 AnnotationAwareAspectJAutoProxyCreator 中完成的。如果没有三级缓存的工厂,我们就必须在实例化后立即为所有 Bean 创建代理,这违背了 Spring 设计的初衷。通过三级缓存,只有在真正发生循环依赖时,才会通过工厂提前创建代理对象;如果没有循环依赖,代理依然在初始化后按原计划创建。

最后需要注意,Spring 并不是万能的。构造器注入的循环依赖它解决不了,因为对象还没实例化就卡住了。另外,原型模式(Prototype)的循环依赖也解决不了,因为 Spring 不会缓存原型 Bean。

在实际开发中,虽然 Spring 帮我们兜底了,但从架构设计角度看,出现循环依赖往往意味着代码耦合度过高。所以即便 Spring 能解决,我也建议尽量通过重构代码逻辑,比如引入中转层或使用 @Lazy 注解来规避。

最后更新于:
⏱️

登录后开始阅读

当前 MVP 版本只开放邮箱密码登录注册。完成登录后即可进入面试题内容。

还没有账号?先去注册