2025年Java面试宝典下载链接(提取码:9b3g)
Spring如何解决循环依赖的原理

每次面试问到Spring循环依赖问题,不少候选人都会提到"三级缓存",但是能真正把底层逻辑讲清楚的却不多。今天我们就从源码层面向下挖,看看Spring到底是怎么玩转这个经典问题的。
循环依赖的两种典型场景
假设现在有两个Service类:OrderService需要注入UserService,而UserService反过来也要注入OrderService。这种属性注入场景下,Spring是怎么处理的呢?
再比如通过@Bean方式配置两个互相依赖的Bean:
@Bean
public A a() { return new A(b()); }
@Bean
public B b() { return new B(a()); }
这种情况还能解决吗?
Spring的破局之道
核心原理
Spring通过提前暴露半成品对象来解决循环依赖。这个思路就像装修房子:当墙面粉刷还没完成时,先把门框安装好,等墙面处理完再回来装门板。
具体来说分为三个关键阶段:
- 实例化:堆内存分配,生成原始对象
- 属性填充:依赖注入阶段
- 初始化:执行init-method等

三级缓存工作机制
Spring内部维护了三个重要的Map:
| 缓存级别 | 存储内容 | 作用 | |----------------|----------------------------------|-------------------------------| | singletonObjects | 完整的Bean实例 | 直接获取可用对象 | | earlySingletonObjects | 半成品对象(未完成属性注入) | 防止重复创建代理对象 | | singletonFactories | ObjectFactory对象 | 处理AOP代理的关键 |
当创建Bean A时:
- 先通过反射实例化A对象
- 将A的ObjectFactory放入三级缓存
- 进行属性注入时发现需要Bean B
- 转而创建Bean B,同样经历这三个步骤
- B在注入时从三级缓存拿到A的ObjectFactory
- ObjectFactory.getObject()返回A的半成品(可能生成代理对象)
- B完成初始化后,A继续完成后续处理
构造器注入为何失效
使用构造器注入时,Spring必须要在实例化阶段就完成所有依赖注入。这时候对象还没创建完成,无法提前暴露半成品,导致死锁。比如:
public class A {
public A(B b) {}
}
public class B {
public B(A a) {}
}
这种情况下,Spring会直接抛出BeanCurrentlyInCreationException异常。

高频面试问题解析
Q1:为什么需要三级缓存而不是二级?
主要是为了解决AOP代理的问题。当存在循环依赖且需要生成代理对象时,ObjectFactory能确保整个容器中同一个Bean始终返回同一个代理对象。
Q2:如何检测循环依赖?
Spring通过ThreadLocal变量记录当前正在创建的Bean名称。如果在创建链中重复出现同一个Bean名,立即抛出异常。
Q3:循环依赖对事务管理有什么影响?
通过动态代理实现的声明式事务,在循环依赖场景下要特别注意代理对象的生成时机。建议优先使用属性注入而非构造器注入。
需要重点提醒的是,很多面试官会追问到Spring源码中的具体实现类。比如DefaultSingletonBeanRegistry这个类中的三个Map,以及AbstractAutowireCapableBeanFactory中的doCreateBean方法流程。如果大家需要系统准备Spring面试题,可以到面试鸭返利网购买会员,通过我的专属链接可返利25元,相当于用更低成本获取全套面试资料。


