当两个Bean形成A→B→A的依赖闭环时,传统IoC容器面临根本性矛盾:Bean的完整初始化需要其所有依赖项已就绪,而闭环中的任何Bean都无法满足该条件。Spring通过三级缓存机制实现"先暴露半成品Bean"的策略,在保证最终一致性的同时打破死锁。
缓存层级 | 数据结构 | 线程安全机制 | 主要作用 |
---|---|---|---|
singletonObjects(一级) | ConcurrentHashMap | 分段锁机制 | 存储完全初始化的单例Bean |
earlySingletonObjects(二级) | HashMap | 单线程访问 | 存储已实例化但未填充属性的Bean |
singletonFactories(三级) | HashMap | 单线程访问 | 存储对象工厂(支持AOP动态代理) |
设计哲学:一级缓存需应对并发访问,故采用ConcurrentHashMap;二三级缓存仅在Bean创建的单线程阶段使用,使用HashMap提升性能。
以A→B→A为例,展示缓存状态演变:
阶段 | A缓存位置 | B缓存位置 | 关键操作 -----|-----------|-----------|--------- A实例化 | 三级缓存(ObjectFactory) | 无 | 调用构造函数生成A原始对象 B实例化 | 三级缓存(ObjectFactory) | 三级缓存(ObjectFactory) | 检测到A依赖,触发A的缓存迁移 B属性填充 | 二级缓存(A原始对象) | 二级缓存(B原始对象) | 从三级缓存获取A并迁移至二级 A初始化完成 | 一级缓存(完整A) | 一级缓存(完整B) | 完成B注入后初始化A
Step 1: 创建Bean A
ObjectFactory
放入三级缓存:javaaddSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
此时A处于"已实例化但未填充属性"状态
Step 2: 注入依赖B
populateBean()
阶段发现需要注入BStep 3: 创建Bean B
Step 4: 解析A的依赖
getObject()
:javaprotected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
return bean; // 非AOP场景直接返回原始对象
}
Step 5: 完成B的初始化
Step 6: 完成A的初始化
在A被代理的情况下,关键差异出现在getEarlyBeanReference()
方法:
javaprotected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object proxy = ProxyFactory.getProxy(bean); // 生成CGLIB或JDK代理对象
return proxy;
}
此时三级缓存返回的是代理对象,保证循环依赖链中注入的始终是代理对象。
三级缓存存储的ObjectFactory
解决了两大难题:
earlyProxyReferences
记录避免多次代理javaprivate final Set<Object> earlyProxyReferences = new HashSet<>();
protected Object getEarlyBeanReference(String beanName, Object bean) {
earlyProxyReferences.add(bean);
return ProxyFactory.getProxy(bean);
}
在getSingleton()
方法中采用双重检查加锁:
javapublic Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 一级缓存检查
if (!containsSingleton(beanName)) {
// 二级缓存检查
if (!containsEarlySingleton(beanName)) {
// 三级缓存处理
addSingletonFactory(beanName, singletonFactory.getObject());
}
}
}
}
这种设计确保在多线程场景下,同一个Bean只会被创建一次。
当A和B通过构造器注入时:
java@Component
public class A {
public A(B b) {} // 构造器注入
}
在实例化A时会直接调用new A(B)
,而此时B尚未创建,必然导致BeanCurrentlyInCreationException
。Spring无法通过三级缓存解决此类问题。
对于@Scope("prototype")
的Bean:
java@Component
@Scope("prototype")
public class A {
@Autowired private B b;
}
每次请求都会创建新实例,无法利用单例缓存机制。Spring会抛出BeanCurrentlyInCreationException
。
对于A→B→C→A的复杂循环,处理逻辑与双层循环完全一致。Spring通过维护singletonsCurrentlyInCreation
集合检测所有层级的循环依赖。
在Spring Boot 2.6+版本中,默认禁用循环依赖:
yamlspring:
main:
allow-circular-references: false
开启该选项后,Spring通过DependencyCircularityBreakingPostProcessor
自动插入LazyResolver
实现延迟注入。这种改进虽然提升了健壮性,但本质上仍建议通过重构消除设计缺陷。
问题类型 | 重构方案 | 实施难度 | 风险等级 |
---|---|---|---|
构造器循环依赖 | 改为Setter注入 | ★☆☆ | 低 |
多层嵌套循环 | 提取公共服务类 | ★★★ | 中 |
AOP代理冲突 | 分离业务逻辑与切面 | ★★☆ | 中 |
原型作用域问题 | 改用@Scope+@Lazy | ★☆☆ | 低 |
启用DEBUG级别日志:
javalogging.level.org.springframework.context.support.AbstractResourceBasedMessageSource=DEBUG
结合BeanCurrentlyInCreationException
的堆栈跟踪,可以快速定位循环链路。
随着Spring 6对Jakarta EE 9的全面支持,可能引入:
这种设计哲学体现了Spring框架"优雅降级"的设计理念——在保持向后兼容的同时,持续优化开发者体验。理解这一机制不仅有助于解决实际问题,更能培养系统的架构思维能力。