一、循环依赖的本质与Spring的破局之道
在Spring容器管理Bean生命周期时,循环依赖如同”死锁”般困扰开发者:当BeanA依赖BeanB,而BeanB又反向依赖BeanA时,两者在初始化阶段会陷入无限等待。这种场景在复杂业务系统中尤为常见,例如订单服务依赖支付服务,而支付服务又需要查询订单状态。
Spring通过三级缓存机制提供了优雅的解决方案,其核心思想在于在Bean完全初始化前,通过代理对象或工厂提前暴露引用。这种设计既保证了依赖注入的完整性,又避免了重复创建导致的性能损耗。值得注意的是,该机制仅适用于单例Bean的Setter/Field注入场景,对构造器注入和原型Bean(Prototype)则无能为力。
二、三级缓存的架构设计
Spring容器内部维护了三个核心Map结构,共同构成循环依赖的解决方案:
1. 一级缓存(singletonObjects)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
作为最终成品仓库,存放已完全初始化、可直接使用的Bean实例。其线程安全设计(ConcurrentHashMap)确保了高并发场景下的稳定性,256的初始容量基于常见应用规模优化。
2. 二级缓存(earlySingletonObjects)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
半成品展厅存储已实例化但未完成属性填充的Bean。当发生循环依赖时,Spring会优先从这里获取早期引用,避免重复创建工厂对象。其非线程安全设计(HashMap)基于使用场景优化,因为二级缓存的访问通常在单线程的Bean初始化流程中。
3. 三级缓存(singletonFactories)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
这是整个机制的核心,存储Bean的ObjectFactory工厂对象。当Bean需要暴露早期引用时,Spring会创建一个Lambda表达式工厂:
() -> getEarlyBeanReference(beanName, mbd, bean);
这种设计支持AOP代理的动态生成,确保即使存在代理对象也能正确处理循环依赖。
三、三级缓存协同工作流程
以经典的A-B循环依赖为例,完整流程可分为六个关键阶段:
1. BeanA实例化阶段
- 调用
createBeanInstance()创建原始对象(未填充属性) - 将BeanA的ObjectFactory存入三级缓存
- 此时BeanA处于”半成品”状态,仅完成构造方法调用
2. BeanA属性填充阶段
- 发现依赖BeanB,触发BeanB的初始化流程
- 此时BeanA的引用已存在于三级缓存,但尚未移至二级缓存
3. BeanB实例化阶段
- 重复BeanA的创建流程,生成原始对象
- 将BeanB的ObjectFactory存入三级缓存
4. BeanB属性填充阶段
- 发现依赖BeanA,触发缓存查找:
- 一级缓存:未找到(BeanA未完成初始化)
- 二级缓存:未找到(BeanA尚未升级至此)
- 三级缓存:找到BeanA的ObjectFactory
- 调用工厂获取早期引用(可能包含代理对象)
- 完成BeanB的属性填充和初始化
- 将BeanB移入一级缓存
5. BeanA完成初始化
- 获得已初始化的BeanB引用
- 完成剩余属性填充和初始化逻辑
- 将BeanA从三级缓存升级至二级缓存(若存在代理则直接存入一级缓存)
- 最终将BeanA移入一级缓存
6. 缓存清理阶段
- BeanA初始化完成后,二级缓存中的引用会被清除
- 确保每个Bean在整个生命周期中只存在于一个缓存层级
四、特殊场景处理与限制
1. 构造器注入的局限性
当使用构造器注入时,Bean必须在实例化阶段就获得所有依赖,此时三级缓存尚未建立,因此无法解决循环依赖。解决方案包括:
- 改用Setter/Field注入
- 重新设计避免循环依赖
- 使用
@Lazy注解延迟加载
2. 原型Bean的不可破解性
原型Bean每次请求都会创建新实例,Spring不会缓存中间状态,因此无法通过提前暴露引用解决循环依赖。最佳实践是:
- 避免在原型Bean间建立循环依赖
- 使用单例Bean作为中介
- 考虑重构设计模式
3. AOP代理的特殊处理
当Bean需要被代理时,三级缓存的工厂机制会动态生成代理对象:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp).getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}
这种设计确保了即使存在循环依赖,代理对象也能正确创建。
五、最佳实践与避坑指南
- 优先使用Setter注入:从架构设计层面避免循环依赖
- 合理规划Bean作用域:原型Bean应保持简单,避免复杂依赖
- 监控缓存状态:在调试循环依赖时,可通过
Debug模式观察三个缓存的内容变化 - 警惕代理对象问题:当结合AOP使用时,确保理解代理对象的创建时机
- 性能优化建议:对于高频创建的Bean,考虑使用对象池模式替代原型作用域
六、源码级验证
通过分析DefaultSingletonBeanRegistry类的关键方法,可以验证三级缓存的工作流程:
// 获取Bean的核心方法protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 从一级缓存获取Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 从二级缓存获取singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 从三级缓存获取工厂并创建早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;}
这段代码清晰展示了三级缓存的查找顺序和升级逻辑,是理解循环依赖解决机制的关键入口。
结语
Spring的三级缓存机制通过精妙的设计,在保证框架灵活性的同时解决了循环依赖这一难题。理解其工作原理不仅能帮助开发者更好地调试依赖问题,更能指导我们在架构设计阶段就规避潜在风险。对于复杂业务系统,建议结合监控工具持续观察缓存状态,确保系统在高并发场景下的稳定性。