Spring的循环依赖问题

📅 2026/7/22 6:50:27 👁️ 阅读次数 📝 编程学习
Spring的循环依赖问题

一、循环依赖是怎么产生的?

最经典的场景就是Bean 之间相互引用

@ComponentclassA{@AutowiredprivateBb;}@ComponentclassB{@AutowiredprivateAa;}

🔁 发生了什么?

  1. Spring 创建 A
  2. A 依赖 B → 去创建 B
  3. 创建 B 时发现依赖 A → 又要创建 A
  4. A 还没创建完成 → 卡住 →循环依赖产生

👉 本质:Bean 创建过程中形成闭环依赖


二、Spring 为什么“默认能解决”?

关键点:只支持“单例 + setter/字段注入”的循环依赖

Spring 的解决方案核心是:

⭐ 三级缓存(重点,面试高频)

singletonObjects 一级缓存:完全初始化好的BeanearlySingletonObjects 二级缓存:提前暴露的Bean(半成品) singletonFactories 三级缓存:Bean工厂(用于生成代理对象)

三、解决流程(核心机制)

用 A 和 B 举例:

🧩 步骤拆解:

  1. 创建 A(此时是“半成品”)
  2. 把 A 的ObjectFactory 放入三级缓存
  3. A 依赖 B → 去创建 B
  4. 创建 B 时依赖 A → 尝试获取 A
  5. 从三级缓存拿到 A 的“早期引用”(可能是代理对象)
  6. B 创建完成
  7. 回过头来完成 A 的初始化

👉 这样就绕开了“死循环”


四、为什么需要三级缓存?

很多人会问:二级缓存不够吗?

答案:不够,因为要解决AOP 代理问题

🎯 关键点:

如果 A 需要被代理(比如事务、AOP):

  • 不能直接暴露原始对象
  • 必须暴露代理对象

👉 三级缓存(ObjectFactory)就是为了:

在“提前暴露”时,可以决定返回普通对象还是代理对象


五、哪些情况解决不了?

Spring 不是万能的,这些情况会直接报错:

❌ 1. 构造器注入(Constructor Injection)

classA{publicA(Bb){}}classB{publicB(Aa){}}

👉 原因:
构造器必须“完整对象”,不能用半成品 → 无法提前暴露


❌ 2. 原型(prototype)作用域 多例Bean

@Scope("prototype")

👉 原因:
Spring 不缓存 prototype Bean → 没法用三级缓存


❌ 3. 循环依赖链过于复杂(含 FactoryBean / 动态代理)

比如:

A → B → C → A

或者涉及:

  • FactoryBean
  • BeanPostProcessor
  • 动态代理(AOP 复杂嵌套)
💥 为什么不行?

👉 提前暴露的对象可能:

  • 不是最终形态(还没代理)
  • 或者代理链没构建完成

→ 最终导致:

Bean 创建异常 / 代理错乱


❌ 4. AOP + 循环依赖的“隐形坑”

例如:

@ServiceclassAService{@AutowiredprivateBServicebService;@TransactionalpublicvoidmethodA(){}}

⚠️ 问题点:

  • 提前暴露的是“早期对象”
  • AOP 代理可能还没织入

👉 结果可能是:

事务、切面不生效

(这种不是启动报错,而是运行时“悄悄出 bug”😈)


❌ 5. Spring Boot 2.6+ 默认限制

Spring Boot 2.6开始:

spring.main.allow-circular-references=false

👉 默认禁止循环依赖

💥 表现:

直接启动失败(哪怕是 setter 注入)


六、如何解决循环依赖问题?

✅ 方法1:改成 setter 注入(推荐)

@AutowiredpublicvoidsetB(Bb){this.b=b;}

👉 让 Spring 有机会“先创建半成品”


✅ 方法2:使用 @Lazy(常用技巧)

@Autowired@LazyprivateBb;

👉 延迟加载,相当于:

“等真正用到你再创建”


✅ 方法3:拆分设计(最佳实践)

比如:

ACBC

👉 引入中间层,打断循环


✅ 方法4:使用接口解耦

AIBBIA

👉 降低耦合,避免强依赖闭环


✅ 方法5:手动获取 Bean

ApplicationContext.getBean()

👉 不推荐,但某些场景可用