【Java后端】Spring 如何解决循环依赖:原理 + 源码解读
Spring 如何解决循环依赖:原理 + 源码解读
- 开篇引导:什么是循环依赖
- Spring 解决循环依赖的思路(三层缓存)
- 源码走读(关键方法:
getSingleton
、doCreateBean
、addSingletonFactory
) - 限制与不能解决的场景
- 实践建议
Spring 如何解决循环依赖(原理 + 源码解读)
目录
-
一、循环依赖是什么
-
二、Spring 的解决思路
-
三、源码走读(三层缓存机制)
- 1.
getSingleton
- 2.
doCreateBean
- 3.
addSingletonFactory
- 1.
-
四、不能解决的场景
-
五、常见解决办法
-
六、总结
一、循环依赖是什么
循环依赖指的是 两个或多个 Bean 互相依赖,导致初始化时出现死循环。
比如:
@Component
public class A {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate 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+ 默认关闭循环依赖,需要显式开启。
- 从设计角度,循环依赖往往是架构问题,最佳做法是重构消除循环。