循环依赖处理
深入理解Spring如何处理循环依赖,掌握三级缓存机制的原理,理解提前暴露、早期引用的创建、代理对象的循环依赖处理等核心机制。
📋 循环依赖概览
循环依赖是指两个或多个Bean相互依赖,形成一个依赖环。Spring通过三级缓存机制巧妙地解决了单例Bean的循环依赖问题。
🎯 一、循环依赖场景
1.1 常见循环依赖场景
场景1:构造函数循环依赖
java
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}场景2:Setter循环依赖
java
@Service
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private ServiceA serviceA;
@Autowired
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
}场景3:字段循环依赖
java
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}1.2 循环依赖的支持情况
| 依赖方式 | 单例Bean | 原型Bean |
|---|---|---|
| 构造器注入 | ❌ 不支持 | ❌ 不支持 |
| Setter/字段注入 | ✅ 支持 | ❌ 不支持 |
为什么构造器注入不支持循环依赖?
构造器在实例化阶段就必须完成依赖注入,此时Bean还未完全创建,无法提前暴露,因此无法解决循环依赖。
🎯 二、三级缓存机制
2.1 三级缓存定义
Spring使用三个Map来解决循环依赖:
java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry
implements SingletonBeanRegistry {
// 一级缓存:存储完全初始化好的单例Bean
private final Map<String, Object> singletonObjects =
new ConcurrentHashMap<>(256);
// 二级缓存:存储早期单例Bean(已实例化,未完成初始化)
private final Map<String, Object> earlySingletonObjects =
new ConcurrentHashMap<>(16);
// 三级缓存:存储单例Bean工厂(ObjectFactory)
private final Map<String, ObjectFactory<?>> singletonFactories =
new HashMap<>(16);
// 正在创建中的Bean名称集合
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}三级缓存的作用:
- 一级缓存(singletonObjects):存储完全初始化好的Bean,可以直接使用
- 二级缓存(earlySingletonObjects):存储早期Bean(已实例化但未初始化),用于解决循环依赖
- 三级缓存(singletonFactories):存储Bean工厂,用于创建早期引用
2.2 三级缓存流程图
🎯 三、循环依赖解决流程
3.1 getSingleton方法
getSingleton方法是获取单例Bean的核心方法,也是解决循环依赖的关键:
java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 先尝试从一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 从三级缓存获取工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 4. 执行工厂方法,获取早期引用
singletonObject = singletonFactory.getObject();
// 5. 将早期引用放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
// 6. 从三级缓存删除工厂
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}执行流程:
- 首先从一级缓存获取完全初始化好的Bean
- 如果一级缓存没有且Bean正在创建中,从二级缓存获取早期Bean
- 如果二级缓存也没有且允许早期引用,从三级缓存获取工厂
- 执行工厂方法获取早期引用,放入二级缓存,删除三级缓存的工厂
- 返回早期引用
3.2 addSingletonFactory方法
在Bean实例化后、属性注入前,将Bean的工厂放入三级缓存:
java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}3.3 完整流程示例
场景:ServiceA依赖ServiceB,ServiceB依赖ServiceA
步骤1:创建ServiceA
java
// 1. getBean("serviceA")
// 2. 从缓存获取(一级、二级、三级都没有)
// 3. 标记serviceA正在创建:singletonsCurrentlyInCreation.add("serviceA")
// 4. 实例化ServiceA(调用构造器)
// 5. 提前暴露:addSingletonFactory("serviceA", () -> getEarlyBeanReference(...))
// 将serviceA的工厂放入三级缓存
// 6. 填充属性,发现需要serviceB步骤2:创建ServiceB
java
// 1. getBean("serviceB")
// 2. 从缓存获取(一级、二级、三级都没有)
// 3. 标记serviceB正在创建:singletonsCurrentlyInCreation.add("serviceB")
// 4. 实例化ServiceB(调用构造器)
// 5. 提前暴露:addSingletonFactory("serviceB", () -> getEarlyBeanReference(...))
// 将serviceB的工厂放入三级缓存
// 6. 填充属性,发现需要serviceA
// 7. getBean("serviceA")
// 8. getSingleton("serviceA", true):
// - 一级缓存没有
// - serviceA正在创建中
// - 从三级缓存获取serviceA的工厂
// - 执行工厂方法获取早期serviceA(可能返回代理对象)
// - 将早期serviceA放入二级缓存
// - 从三级缓存删除serviceA的工厂
// - 返回早期serviceA
// 9. 将早期serviceA注入到serviceB
// 10. 完成serviceB的初始化
// 11. addSingleton("serviceB", serviceB):将serviceB放入一级缓存,删除二级、三级缓存
// 12. 返回serviceB步骤3:完成ServiceA的创建
java
// 1. 将serviceB注入到serviceA
// 2. 完成serviceA的初始化
// 3. addSingleton("serviceA", serviceA):
// - 将serviceA放入一级缓存
// - 从二级缓存删除serviceA(如果存在)
// - 从三级缓存删除serviceA的工厂(如果存在)
// 4. 返回serviceA🎯 四、代理对象的循环依赖
4.1 代理对象的处理
如果Bean需要被AOP代理,循环依赖的处理会更加复杂。
getEarlyBeanReference方法:
java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}AbstractAutoProxyCreator.getEarlyBeanReference:
java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}处理流程:
- 在提前暴露时,如果Bean需要被代理,调用
getEarlyBeanReference创建代理对象 - 代理对象放入二级缓存,供其他Bean注入
- 在Bean初始化完成后,检查是否需要重新创建代理
- 如果已经创建过早期代理,直接返回该代理,不再重复创建
4.2 代理对象循环依赖示例
java
@Service
@Transactional
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
@Transactional
public class ServiceB {
@Autowired
private ServiceA serviceA;
}处理流程:
- 创建ServiceA实例
- 提前暴露时,创建ServiceA的代理对象(因为@Transactional)
- ServiceA代理对象放入二级缓存
- 创建ServiceB实例
- ServiceB注入ServiceA时,从二级缓存获取ServiceA代理对象
- 提前暴露时,创建ServiceB的代理对象
- ServiceB代理对象放入二级缓存
- ServiceA注入ServiceB时,从二级缓存获取ServiceB代理对象
- 完成初始化,将代理对象放入一级缓存
🎯 五、为什么原型Bean不支持循环依赖
5.1 原型Bean的特点
- 每次
getBean都会创建新实例 - 不会放入单例缓存
- 生命周期由调用方管理
5.2 为什么不支持
原因分析:
- 无法缓存:原型Bean不会放入单例缓存,无法提前暴露
- 无限递归:如果支持,每次创建都会触发依赖的创建,导致无限递归
- 内存泄漏:无法追踪正在创建的原型Bean,可能导致内存泄漏
Spring的检查:
java
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
if (beanNameSet.contains(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
beanNameSet.add(beanName);
}
}如果检测到原型Bean的循环依赖,会抛出BeanCurrentlyInCreationException异常。
📖 学习资源
视频教程
书籍推荐
- 《Spring源码深度解析》 - 郝佳
- 《Spring技术内幕》 - 计文柯
网站资源
✅ 学习检查清单
- [ ] 已理解循环依赖的概念和场景
- [ ] 已理解三级缓存机制的作用
- [ ] 已理解getSingleton方法的执行流程
- [ ] 已理解提前暴露的原理
- [ ] 已理解代理对象的循环依赖处理
- [ ] 已理解为什么构造器注入不支持循环依赖
- [ ] 已理解为什么原型Bean不支持循环依赖
- [ ] 已绘制循环依赖解决流程图
- [ ] 已总结核心知识点
上一章节:Bean生命周期
下一章节:设计模式应用
返回目录:Spring源码深度解析