Spring解决循环依赖的原理源码解析
作者:柯南二号
循环依赖指的是两个或多个Bean互相依赖,导致初始化时出现死循环,本文给大家介绍Spring解决循环依赖的原理 + 源码解读,感兴趣的朋友跟随小编一起看看吧
Spring 如何解决循环依赖:原理 + 源码解读
- 开篇引导:什么是循环依赖
- Spring 解决循环依赖的思路(三层缓存)
- 源码走读(关键方法:
getSingleton
、doCreateBean
、addSingletonFactory
) - 限制与不能解决的场景
- 实践建议
Spring 如何解决循环依赖(原理 + 源码解读)
一、循环依赖是什么
循环依赖指的是 两个或多个 Bean 互相依赖,导致初始化时出现死循环。
比如:
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }
- 构造器注入:直接死锁,无法解决。
- Setter/Field 注入:Spring 可以通过“提前暴露半成品对象”来打破循环。
二、Spring 的解决思路
Spring 采用 三层缓存机制 + 提前暴露引用 来解决单例 Bean 的循环依赖。
三层缓存:
- singletonObjects:一级缓存,存放完全初始化好的单例 Bean。
- earlySingletonObjects:二级缓存,存放早期的半成品 Bean。
- singletonFactories:三级缓存,存放一个
ObjectFactory
,用于生成早期引用(可能是代理对象)。
流程简化:
- Bean 创建时,先放入三级缓存(
singletonFactories
)。 - 如果别的 Bean 依赖它,就能通过三级缓存拿到早期引用。
- 依赖注入完成后,Bean 初始化成功,放入一级缓存,并清理二、三级缓存。
三、源码走读(三层缓存机制)
1.getSingleton
在 DefaultSingletonBeanRegistry
中:
public Object getSingleton(String beanName, boolean allowEarlyReference) { // 1. 先从一级缓存中取 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 2. 一级没有,且 Bean 正在创建中,则尝试二级缓存 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 3. 二级没有,就从三级缓存拿 ObjectFactory 生成早期引用 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 提前曝光的对象放到二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; }
👉 这里是 循环依赖能被打破的关键点:
- A 需要 B,发现 B 还在创建中,去二级/三级缓存里取。
- 如果三级缓存有工厂,就生成一个“早期引用”,保证注入能继续。
2.doCreateBean
在 AbstractAutowireCapableBeanFactory
中,Bean 的创建核心流程:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { // 1. 实例化 Bean(构造方法) BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); Object bean = instanceWrapper.getWrappedInstance(); // 2. 是否需要提前暴露? boolean earlySingletonExposure = (mbd.isSingleton() && allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 加入三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 3. 填充属性(setter/field 注入) populateBean(beanName, mbd, instanceWrapper); // 4. 初始化(各种回调、AOP 代理) bean = initializeBean(beanName, bean, mbd); // 5. 放入一级缓存,清理二、三级缓存 registerSingleton(beanName, bean); return bean; }
👉 关键点:在属性注入之前,就把早期引用工厂放入三级缓存。
这样如果别的 Bean 需要它,可以从三级缓存里拿到“半成品”。
3.addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); } }
👉 确保只有在 Bean 没有完全初始化前,才会暴露到三级缓存。
四、不能解决的场景
- 构造器注入的循环依赖
- 因为实例化必须先完成构造器,Spring 没法提前暴露半成品。
- 原型作用域(prototype)的循环依赖
- 每次创建都是新对象,不能共享早期引用。
- 部分 AOP/异步场景
- 代理对象和早期引用可能不一致,导致注入问题。
- Spring Boot 2.6+ 默认禁止循环依赖
- 需显式开启:
spring.main.allow-circular-references=true
五、常见解决办法
- 重构依赖关系(最佳实践)
- 拆出第三个 Service,消除循环。
- 使用
@Lazy
延迟加载
public A(@Lazy B b) { this.b = b; }
使用 ObjectProvider
或 Provider
@Autowired private ObjectProvider<B> bProvider;
ApplicationContext#getBean 延迟获取(不推荐,耦合度高)。
六、总结
- Spring 通过 三级缓存(singletonObjects / earlySingletonObjects / singletonFactories) 和 getEarlyBeanReference,解决了大部分 单例 + Setter/Field 注入 的循环依赖。
- 构造器循环依赖、prototype Bean 无法解决。
- Spring Boot 2.6+ 默认关闭循环依赖,需要显式开启。
- 从设计角度,循环依赖往往是架构问题,最佳做法是重构消除循环。
到此这篇关于Spring解决循环依赖的原理源码解析的文章就介绍到这了,更多相关Spring循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!