Spring的循环依赖问题
一、循环依赖是怎么产生的?
最经典的场景就是Bean 之间相互引用:
@ComponentclassA{@AutowiredprivateBb;}@ComponentclassB{@AutowiredprivateAa;}🔁 发生了什么?
- Spring 创建 A
- A 依赖 B → 去创建 B
- 创建 B 时发现依赖 A → 又要创建 A
- A 还没创建完成 → 卡住 →循环依赖产生
👉 本质:Bean 创建过程中形成闭环依赖
二、Spring 为什么“默认能解决”?
关键点:只支持“单例 + setter/字段注入”的循环依赖
Spring 的解决方案核心是:
⭐ 三级缓存(重点,面试高频)
singletonObjects 一级缓存:完全初始化好的BeanearlySingletonObjects 二级缓存:提前暴露的Bean(半成品) singletonFactories 三级缓存:Bean工厂(用于生成代理对象)三、解决流程(核心机制)
用 A 和 B 举例:
🧩 步骤拆解:
- 创建 A(此时是“半成品”)
- 把 A 的ObjectFactory 放入三级缓存
- A 依赖 B → 去创建 B
- 创建 B 时依赖 A → 尝试获取 A
- 从三级缓存拿到 A 的“早期引用”(可能是代理对象)
- B 创建完成
- 回过头来完成 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:拆分设计(最佳实践)
比如:
A→CB→C👉 引入中间层,打断循环
✅ 方法4:使用接口解耦
A→IBB→IA👉 降低耦合,避免强依赖闭环
✅ 方法5:手动获取 Bean
ApplicationContext.getBean()👉 不推荐,但某些场景可用
