一、循环依赖的本质与解决方案演进
1.1 循环依赖的典型场景
在Spring容器中,当两个或多个Bean互相持有对方引用时即构成循环依赖。例如:
@Servicepublic class ServiceA {@Autowiredprivate ServiceB serviceB;}@Servicepublic class ServiceB {@Autowiredprivate ServiceA serviceA;}
这种设计在业务中并不罕见,但若处理不当会导致Spring容器启动失败,抛出BeanCurrentlyInCreationException异常。
1.2 解决方案的演进路径
Spring通过缓存机制逐步优化循环依赖处理:
- 无缓存阶段:早期版本采用递归实例化,遇到循环依赖直接抛出异常
- 二级缓存方案:通过
singletonObjects(一级)和earlySingletonObjects(二级)缓存实现基础循环依赖支持 - 三级缓存完善:引入
singletonFactories解决AOP代理场景下的对象一致性难题
二、三级缓存架构详解
2.1 缓存层级结构
Spring通过三个Map结构协同工作:
// 一级缓存:完全初始化的Beanprivate final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二级缓存:原始Bean对象(未代理)private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三级缓存:ObjectFactory工厂private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2.2 关键设计原则
- 对象状态隔离:通过不同缓存区分原始对象与代理对象
- 延迟代理创建:在最后阶段才生成AOP代理,避免重复代理
- 线程安全保障:使用ConcurrentHashMap保证并发场景下的数据一致性
三、三级缓存工作原理
3.1 完整实例化流程
以ServiceA→ServiceB→ServiceA的循环依赖为例:
-
ServiceA实例化:
- 创建原始对象(未填充属性)
- 存入三级缓存(
singletonFactories)
-
ServiceB实例化:
- 创建原始对象
- 填充属性时发现依赖ServiceA
- 从三级缓存获取ServiceA的
ObjectFactory - 调用
getObject()获取早期引用(此时可能创建代理) - 将代理对象存入二级缓存(
earlySingletonObjects)
-
ServiceA完成初始化:
- 执行AOP逻辑生成最终代理对象
- 从二级缓存升级到一级缓存
- 清理二级缓存中的临时对象
3.2 代理对象创建时机
关键代码逻辑:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;// 检查是否需要AOP代理if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp).getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;}
通过SmartInstantiationAwareBeanPostProcessor接口的getEarlyBeanReference方法,在对象暴露前创建代理,确保后续属性注入时使用的是代理对象而非原始对象。
四、二级缓存的局限性分析
4.1 无法处理的场景
当循环依赖链中包含需要AOP代理的Bean时,二级缓存方案会失效:
@Service@Transactional // 需要AOP代理public class TransactionalService {@Autowiredprivate CircularService circularService;}@Servicepublic class CircularService {@Autowiredprivate TransactionalService transactionalService;}
二级缓存方案会导致:
- 第一次获取到的是原始对象(未代理)
- 完成初始化后生成代理对象
- 属性注入时对象引用不一致,引发不可预测行为
4.2 三级缓存的优势
通过ObjectFactory的延迟代理机制:
- 保证每次获取的都是最新代理对象
- 避免重复代理(如同时存在
@Transactional和@Async) - 维持对象引用的单向性,防止循环引用导致的内存泄漏
五、最佳实践与性能优化
5.1 设计建议
- 避免复杂循环依赖:通过重构解耦业务逻辑
- 合理使用代理:仅在必要场景添加
@Transactional等注解 - 监控依赖关系:使用
ApplicationContext的getDependenciesForBean方法检测潜在循环
5.2 性能优化技巧
- 缓存预热:在启动阶段通过
@DependsOn显式指定初始化顺序 - 减少代理:对不需要事务等方法级别的代理,改用编程式事务
- 异步初始化:对非核心Bean使用
@Lazy延迟加载
六、常见问题解答
Q1:为什么不能直接用三级缓存替代二级缓存?
三级缓存的核心是ObjectFactory,其创建开销大于直接存储对象。对于非代理场景,二级缓存更高效。Spring通过addSingletonFactory方法的条件判断,仅在必要时才创建工厂对象。
Q2:三级缓存会导致内存泄漏吗?
Spring在Bean初始化完成后会清理二级缓存,且三级缓存的ObjectFactory是弱引用持有,正常流程下不会造成内存泄漏。但需注意避免在BeanPostProcessor中持有强引用。
Q3:如何调试循环依赖问题?
- 启用DEBUG日志:
logging.level.org.springframework.context=DEBUG - 使用Actuator的
beans端点查看依赖关系 - 通过
AbstractApplicationContext.getBeanFactory()获取DefaultListableBeanFactory进行深度分析
结语
Spring的三级缓存机制是框架设计中的经典范例,其通过分层缓存与延迟代理的结合,完美解决了循环依赖与AOP代理的协同问题。理解这一机制不仅有助于解决实际问题,更能提升对Spring核心原理的认知深度。在实际开发中,应遵循”避免优于解决”的原则,通过合理设计减少循环依赖的出现,而非过度依赖框架的容错机制。