Spring循环依赖:三级缓存解决循环引用的完整推导过程
Spring循环依赖:三级缓存解决循环引用的完整推导过程
适读人群:想搞清楚Spring循环依赖底层原理的Java开发者 | 阅读时长:约20分钟
开篇故事
有同学问我:Spring 是怎么解决循环依赖的,为什么需要三级缓存,两级不行吗?
我当时回答了他,但事后发现说得不太清楚。这个问题的答案不是"为了解决循环依赖"这么简单,而是要从"如果只有一级缓存会怎样,如果只有两级缓存会怎样"来反推,才能真正理解为什么是三级。
这是一个需要完整推导的问题,今天把推导过程一步一步写清楚。
一、什么是循环依赖
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB; // A依赖B
}@Service
public class ServiceB {
@Autowired
private ServiceA serviceA; // B依赖A,形成循环
}如果按照"先完整创建A,再创建B"的策略:
- 创建A → 注入B → B还没创建,去创建B → 注入A → A还没创建完... → 无限循环
二、三级缓存的完整推导
2.1 Spring 的三级缓存定义
// DefaultSingletonBeanRegistry.java
public class DefaultSingletonBeanRegistry {
/** 一级缓存:完整的Bean(实例化+属性注入+初始化全部完成)*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 二级缓存:早期暴露的Bean(实例化完成但属性还没注入)*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** 三级缓存:Bean工厂(Lambda,用于在需要时生成代理对象)*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}2.2 如果只有一级缓存
一级缓存不够,因为完成的Bean才放入一级缓存,而在A完成之前B就需要A,死锁。
2.3 如果只有两级缓存(一级+二级)
把半成品(刚实例化、还没注入属性)也放入缓存:
两级缓存能解决普通 Bean 的循环依赖!但问题来了……
2.4 为什么需要三级缓存:AOP 场景
如果 ServiceA 需要被 AOP 代理(比如加了 @Transactional),情况变了:
Spring的设计:Bean 完成初始化后,在 BeanPostProcessor.postProcessAfterInitialization 里创建代理。
也就是说:代理对象的创建是在 步骤11(初始化完成后),不是在 步骤1(实例化后)。问题:如果只有两级缓存,ServiceB 从二级缓存拿到的是 ServiceA 的原始对象(未代理的),而最终 Spring 容器里的 ServiceA 是一个代理对象。ServiceB 持有的引用就和容器里的不是同一个对象了!
三级缓存的解决方案:
在实例化 Bean 之后,立即把一个 ObjectFactory(工厂 Lambda) 放入三级缓存。当其他 Bean 需要这个"半成品"时,通过工厂 Lambda 获取——如果需要代理,工厂会提前创建代理并放入二级缓存;如果不需要代理,工厂返回原始对象。
三、核心源码追踪
3.1 getSingleton 的三级缓存查找逻辑
// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先查一级缓存(完整Bean)
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 查二级缓存(早期暴露的引用)
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 双重检查
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 查三级缓存(ObjectFactory工厂)
ObjectFactory<?> singletonFactory =
this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用工厂获取早期引用(可能触发代理创建)
singletonObject = singletonFactory.getObject();
// 升级到二级缓存,移出三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}3.2 三级缓存中工厂 Lambda 的注册
// AbstractAutowireCapableBeanFactory.doCreateBean()
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. 如果是单例且允许循环引用,提前暴露到三级缓存
boolean earlySingletonExposure = (mbd.isSingleton() &&
this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 这个Lambda就是三级缓存里的ObjectFactory
}
// 3. 属性注入(可能触发循环依赖,此时会调用三级缓存的工厂)
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化
Object exposedObject = initializeBean(beanName, bean, mbd);
return exposedObject;
}
/**
* 获取早期Bean引用(三级缓存工厂调用的方法)
* 如果Bean需要代理(有AOP增强),这里会提前创建代理
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
// AbstractAutoProxyCreator 实现了这个方法,在这里提前创建代理
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}四、踩坑实录
坑1:构造器注入的循环依赖无法解决
Spring 的三级缓存只对 setter 注入(或字段注入) 的循环依赖有效。
根因:三级缓存在"实例化之后"才放入,但构造器注入在"实例化时"就需要依赖,此时连半成品都还没有,三级缓存帮不上忙。
// 这种循环依赖Spring解决不了,启动报错
@Service
public class ServiceA {
private final ServiceB serviceB;
// 构造器注入 → 实例化时就需要B → B还没创建 → 死锁
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}解决方案:打破循环,或者用 @Lazy 让其中一方延迟注入:
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) { // 注入的是ServiceB的代理,延迟真实注入
this.serviceB = serviceB;
}
}坑2:Spring Boot 2.6+ 默认禁止循环依赖
症状:升级 Spring Boot 到 2.6 或更高版本后,应用启动报错:The dependencies of some of the beans in the application context form a cycle。
根因:Spring Boot 2.6 开始,spring.main.allow-circular-references 默认改为 false,要显式开启才允许循环依赖。
临时解决(不推荐):
spring:
main:
allow-circular-references: true正确解法:重构代码,消除循环依赖。通常是设计问题:
- 把 A 和 B 的共同依赖提取到 C 中
- 用事件机制替代直接调用
坑3:原型(Prototype)Bean 的循环依赖无法解决
症状:两个 @Scope("prototype") 的 Bean 互相依赖,每次获取都报 BeanCurrentlyInCreationException。
根因:三级缓存只针对单例(Singleton)Bean,原型 Bean 每次都要重新创建,没有缓存来打破循环。
坑4:注入的是代理对象,但原始对象也被持有
症状:@Transactional 的 ServiceA 在循环依赖场景下,某个服务持有的 serviceA 引用是原始对象,事务不生效。
排查:这通常是因为三级缓存的工厂 Lambda 没有被正确调用(没有触发循环依赖路径),导致持有的是原始对象而非代理。
验证方式:
// 在注入处检查是否是代理
@Autowired
private ServiceA serviceA;
@PostConstruct
public void check() {
System.out.println("是否是代理: " + AopUtils.isAopProxy(serviceA));
// 如果输出 false,说明注入的是原始对象,事务不会生效
}五、总结与延伸
三级缓存总结:
| 缓存 | 存放内容 | 作用 |
|---|---|---|
| 一级(singletonObjects) | 完整Bean | 正常使用 |
| 二级(earlySingletonObjects) | 早期引用(可能是代理) | 存储已通过工厂生成的早期引用 |
| 三级(singletonFactories) | ObjectFactory | 在需要时生成早期引用,支持 AOP 提前代理 |
为什么需要三级而不是两级:因为 AOP 代理默认在初始化完成后才创建,但循环依赖要求提前暴露 Bean 引用。三级缓存里的工厂 Lambda 解决了"提前生成代理"的问题,保证被循环引用的对象和最终放入容器的对象是同一个实例。
