Spring循环依赖解析:三级缓存机制与源码深度剖析
一、什么是循环依赖?
1.1 循环依赖的定义
循环依赖是指两个或多个Bean相互依赖,形成闭环引用关系。常见场景:
// 场景一:直接相互依赖
@Component
public class BeanA {
@Autowired
private BeanB b;
}
@Component
public class BeanB {
@Autowired
private BeanA a;
}
// 场景二:间接循环依赖
@Component
public class BeanA {
@Autowired
private BeanB b;
}
@Component
public class BeanB {
@Autowired
private BeanC c;
}
@Component
public class BeanC {
@Autowired
private BeanA a; // 形成A->B->C->A的循环
}
1.2 循环依赖的危害
如果没有特殊处理,循环依赖会导致:
-
栈溢出:无限递归创建Bean
-
死锁:多线程环境下可能发生
-
启动失败:Spring容器无法正常初始化
二、手写实现:三级缓存解决循环依赖
2.1 简化版三级缓存实现
public class SimpleSpringContainer {
// 一级缓存:完整Bean(单例池)
private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
// 二级缓存:早期Bean(已实例化但未完全初始化)
private static Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存:ObjectFactory(用于生成早期引用,支持AOP)
private static Map<String, ObjectFactory> singletonFactories = new HashMap<>();
// 正在创建的Bean集合
private static Set<String> singletonsCurrentlyInCreation = new HashSet<>();
// Bean定义缓存
private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
// 获取Bean的核心方法
public static Object getBean(String beanName) throws Exception {
// 1. 先从缓存获取
Object singleton = getSingleton(beanName);
if (singleton != null) {
return singleton;
}
// 2. 标记为正在创建
if (!singletonsCurrentlyInCreation.contains(beanName)) {
singletonsCurrentlyInCreation.add(beanName);
}
// 3. 实例化(调用构造方法)
RootBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
Class<?> beanClass = beanDefinition.getBeanClass();
Object instance = beanClass.newInstance(); // 简化:使用无参构造
// 4. 添加到三级缓存(暴露早期引用)
singletonFactories.put(beanName, () -> {
// 这里可以插入AOP代理创建逻辑
return instance; // 简化:直接返回原始对象
});
// 5. 属性填充(这里可能触发循环依赖)
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
String dependencyName = field.getName(); // 简化:按字段名查找
Object dependency = getBean(dependencyName); // 递归获取依赖
field.set(instance, dependency);
}
}
// 6. 初始化(执行init-method等)
// ... 初始化逻辑
// 7. 添加到一级缓存,清理其他缓存
singletonObjects.put(beanName, instance);
earlySingletonObjects.remove(beanName);
singletonFactories.remove(beanName);
return instance;
}
// 获取单例Bean
private static Object getSingleton(String beanName) {
// 1. 从一级缓存获取
Object bean = singletonObjects.get(beanName);
if (bean != null) {
return bean;
}
// 2. 如果正在创建,检查二级缓存
if (singletonsCurrentlyInCreation.contains(beanName)) {
bean = earlySingletonObjects.get(beanName);
// 3. 二级缓存没有,从三级缓存获取
if (bean == null) {
ObjectFactory factory = singletonFactories.get(beanName);
if (factory != null) {
bean = factory.getObject(); // 可能返回代理对象
earlySingletonObjects.put(beanName, bean); // 升级到二级缓存
singletonFactories.remove(beanName); // 清理三级缓存
}
}
}
return bean;
}
}
2.2 三级缓存的工作流程

三、为什么需要三级缓存?
3.1 缓存级别的分工
| 缓存级别 | 存储内容 | 作用 | 生命周期 |
|---|---|---|---|
| 一级缓存 | 完整的单例Bean | 最终的Bean对象,可直接使用 | 容器生命周期内 |
| 二级缓存 | 早期Bean对象 | 已实例化但未完全初始化的Bean | 解决循环依赖期间 |
| 三级缓存 | ObjectFactory | 生成早期引用的工厂,支持AOP | 实例化后到初始化前 |
3.2 为什么需要二级缓存?
核心作用:分离成熟Bean和不完整Bean,保证并发安全。
问题场景:如果没有二级缓存
// 线程1:正在创建BeanA(属性还未填充) // 线程2:同时请求BeanA // 如果只有一级缓存: // 线程2可能获取到不完整的BeanA(属性为null或未初始化) // 要解决这个问题需要锁住整个getBean方法,性能极差
解决方案:二级缓存存储早期对象
-
已创建但未初始化的Bean放入二级缓存
-
其他线程可以从二级缓存获取早期引用
-
完成初始化的Bean从二级缓存升级到一级缓存
-
锁粒度更小,性能更高
3.3 为什么需要三级缓存?
核心作用:延迟AOP代理创建,保持Bean生命周期规范。
问题场景:如果只有二级缓存
// BeanA需要AOP代理 // 情况1:BeanA被循环依赖 // 如果在实例化后立即创建代理,破坏了Bean生命周期(代理应该在初始化后创建) // 情况2:BeanA被多次循环依赖 // 可能创建多个代理对象,造成混乱
解决方案:三级缓存存储ObjectFactory
singletonFactories.put(beanName, () -> {
// 这个lambda在需要时才执行
return getEarlyBeanReference(beanName, bean);
});
优势:
-
按需创建代理:只有发生循环依赖时才通过ObjectFactory创建代理
-
单例保证:多次获取都从同一个ObjectFactory获取,保证代理对象唯一
-
生命周期完整:普通Bean依然在初始化后创建代理
四、Spring源码深度解析
4.1 三级缓存在源码中的定义
// DefaultSingletonBeanRegistry.java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry
implements SingletonBeanRegistry {
// 一级缓存:完整单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期单例对象(已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:单例工厂(用于创建早期引用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 正在创建的单例Bean名称集合
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}
4.2 核心方法:getSingleton
// 5.3.10版本优化后的getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 快速检查(无锁)
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;
}
5.3.10版本优化:
-
降低锁粒度:二级缓存检查放在锁外
-
避免死锁:
allowEarlyReference=false时不进入同步块 -
提升并发:减少锁竞争
4.3 循环依赖解决流程
步骤1:创建BeanA实例
// AbstractAutowireCapableBeanFactory.createBean()
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args)
throws BeanCreationException {
// 1. 实例化
Object beanInstance = doCreateBean(beanName, mbd, args);
// 2. 添加到三级缓存(在initializeBean之前)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
// 3. 属性填充(可能触发循环依赖)
populateBean(beanName, mbd, new BeanWrapperImpl(beanInstance));
// 4. 初始化
beanInstance = initializeBean(beanName, beanInstance, mbd);
return beanInstance;
}
// 暴露早期引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.singletonObjects.remove(beanName);
}
步骤2:BeanA依赖BeanB,触发循环
// populateBean中解析到@Autowired注解
// 调用beanFactory.getBean("beanB")
// BeanB的创建过程中又依赖BeanA
// 调用getSingleton("beanA")获取早期引用
步骤3:获取早期引用
// 对于AOP代理,getEarlyBeanReference会创建代理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 应用SmartInstantiationAwareBeanPostProcessor
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
return exposedObject;
}
关键点:AnnotationAwareAspectJAutoProxyCreator会在这里创建AOP代理。
步骤4:处理代理对象
// 初始化完成后检查是否需要替换为代理对象
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 如果初始化过程中对象没有改变(没有其他BeanPostProcessor修改)
if (exposedObject == bean) {
exposedObject = earlySingletonReference; // 使用早期代理对象
}
// 如果对象已被修改,抛出异常
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
throw new BeanCurrentlyInCreationException(beanName, "Bean发生改变");
}
}
}
五、特殊情况分析
5.1 为什么不能解决构造器循环依赖?
@Component
public class BeanA {
private BeanB b;
@Autowired
public BeanA(BeanB b) { // 构造器注入
this.b = b;
}
}
@Component
public class BeanB {
private BeanA a;
@Autowired
public BeanB(BeanA a) { // 构造器注入
this.a = a;
}
}
原因分析:
-
时机问题:构造器调用发生在实例化阶段,此时Bean还未创建
-
缓存未就绪:三级缓存在
addSingletonFactory()中添加,而该方法在实例化之后才调用 -
无法获取引用:构造器需要完整对象,无法使用早期引用
错误流程:
创建BeanA -> 调用构造器 -> 需要BeanB -> 创建BeanB -> 调用构造器 -> 需要BeanA -> BeanA未放入缓存 -> 抛出异常
5.2 为什么多例Bean不能解决循环依赖?
@Component
@Scope("prototype")
public class PrototypeBeanA {
@Autowired
private PrototypeBeanB b;
}
@Component
@Scope("prototype")
public class PrototypeBeanB {
@Autowired
private PrototypeBeanA a;
}
原因分析:
-
无缓存机制:多例Bean每次
getBean都创建新对象,不放入单例池 -
循环创建:A依赖B -> 创建B -> B依赖A -> 创建新A -> 新A依赖B -> 无限循环
5.3 AOP下的循环依赖注意事项
@Async导致的问题
@Component
public class ServiceA {
@Autowired
private ServiceB b;
@Async // 会在初始化后创建代理,改变Bean对象
public void asyncMethod() {
// ...
}
}
@Component
public class ServiceB {
@Autowired
private ServiceA a; // 注入的是原始对象,不是代理
}
问题:
-
ServiceA早期引用(三级缓存)是原始对象
-
ServiceB注入的是这个原始对象
-
ServiceA初始化后,@Async会创建新代理对象
-
ServiceB中的ServiceA引用与最终对象不一致
解决方案:使用@Lazy延迟注入
@Component
public class ServiceB {
@Lazy
@Autowired
private ServiceA a; // 注入代理,使用时再解析
}
@Transactional为什么没问题?
@Component
public class ServiceA {
@Autowired
private ServiceB b;
@Transactional // 事务代理
public void transactionalMethod() {
// ...
}
}
原因:事务代理和AOP代理是同一个代理对象,在getEarlyBeanReference()中已创建。
5.4 Spring Boot 2.6+的循环依赖配置
# application.yml
spring:
main:
allow-circular-references: false # 默认关闭循环依赖支持
关闭后如何解决:
-
代码重构:消除循环依赖
-
使用@Lazy:延迟注入
-
Setter注入:替换构造器注入
-
中间类:引入第三方协调依赖
// 使用@Lazy解决
@Component
public class BeanA {
@Lazy
@Autowired
private BeanB b; // 注入代理对象
}
@Component
public class BeanB {
@Autowired
private BeanA a;
}
六、面试常见问题
Q1:Spring如何解决循环依赖?
答:Spring通过三级缓存机制解决循环依赖:
-
一级缓存:
singletonObjects,存储完整的单例Bean -
二级缓存:
earlySingletonObjects,存储早期Bean(已实例化未初始化) -
三级缓存:
singletonFactories,存储ObjectFactory,用于创建早期引用(支持AOP代理)
Q2:为什么需要三级缓存,二级不行吗?
答:需要三级缓存的主要原因:
-
支持AOP代理:在循环依赖时按需创建代理,保持Bean生命周期规范
-
保证代理单例:通过ObjectFactory保证多次获取的是同一个代理对象
-
分离职责:三级缓存负责创建,二级缓存负责存储,一级缓存负责最终结果
Q3:什么情况下循环依赖无法解决?
答:以下情况无法解决:
-
构造器注入:实例化前无法获取依赖
-
多例Bean:无缓存机制
-
@Async注解:初始化后创建新代理,导致引用不一致
-
@Bean方法互相调用:非Spring管理的依赖
Q4:Spring Boot 2.6为什么默认关闭循环依赖?
答:原因包括:
-
代码规范:循环依赖是糟糕的设计,应该避免
-
维护困难:循环依赖增加代码复杂度
-
性能考虑:三级缓存增加内存和CPU开销
-
未来兼容:Spring未来版本可能移除该特性
Q5:如何检测和避免循环依赖?
答:
-
代码审查:检查相互依赖的类
-
架构设计:使用依赖倒置、单一职责原则
-
工具检测:使用SonarQube等代码质量工具
-
测试验证:编写启动测试验证无循环依赖
七、最佳实践
7.1 避免循环依赖的设计原则
-
单一职责原则:每个类只做一件事
-
依赖倒置原则:依赖抽象,不依赖具体
-
接口隔离原则:使用小接口而非大接口
-
层次化设计:明确依赖方向(上层依赖下层)
7.2 必须使用循环依赖时的建议
// 1. 使用@Lazy(推荐)
@Component
public class ServiceA {
@Lazy
@Autowired
private ServiceB b;
}
// 2. 使用Setter注入代替构造器注入
@Component
public class ServiceA {
private ServiceB b;
@Autowired
public void setB(ServiceB b) {
this.b = b;
}
}
// 3. 使用ApplicationContextAware
@Component
public class ServiceA implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
public void method() {
ServiceB b = context.getBean(ServiceB.class); // 使用时获取
}
}
7.3 监控和调试
// 1. 添加循环依赖检测
@Component
public class CircularDependencyDetector implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof ApplicationContextAware) {
// 可以记录Bean的依赖关系
}
return bean;
}
}
// 2. 使用Spring Actuator监控
// application.yml
management:
endpoints:
web:
exposure:
include: beans
endpoint:
beans:
enabled: true
八、总结
Spring的三级缓存机制是其IoC容器中最精妙的设计之一,它巧妙地解决了循环依赖这个经典难题。通过:
-
三级缓存分离:职责明确,性能优化
-
ObjectFactory延迟创建:支持AOP等扩展
-
并发安全设计:多线程环境下稳定工作
-
灵活的配置选项:支持开启/关闭
然而,我们需要认识到:
-
循环依赖是设计缺陷,应尽量避免
-
Spring的解决方案是妥协,不是鼓励
-
理解原理比使用更重要,知其然知其所以然
更多推荐

所有评论(0)