1. 从死锁到三级缓存:Spring循环依赖的本质解析
当两个Bean互相等待对方完成初始化时,就形成了典型的循环依赖场景。这就像两个人在狭窄的走廊相遇,都坚持让对方先通过,结果谁都动不了。Spring通过三级缓存机制巧妙地解决了这个问题,其核心思路类似于交通调度中的"临时借道"策略。
在Spring容器启动时,Bean的创建过程分为实例化(allocate)和初始化(initialize)两个阶段。循环依赖之所以能解决,关键在于将这两个阶段分离。Spring允许Bean在未完成初始化时提前暴露引用,这种"半成品"状态就像建筑工地先搭好钢结构,内部装修可以后续进行。
重要提示:构造器注入的循环依赖无法解决,因为Java语言层面要求构造器必须完整执行。这就像必须先把大楼地基完全打好才能继续施工,没有变通余地。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三级缓存工作机制深度拆解
2.1 缓存层级架构解剖
Spring使用三个Map结构构成分级缓存体系:
java复制// 一级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存放早期暴露的Bean(已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:存放ObjectFactory,用于处理AOP代理
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2.2 典型解决流程示例
以A依赖B,B又依赖A的case为例:
- 开始创建A,实例化后放入三级缓存(singletonFactories)
- 填充A的属性时发现需要B,触发B的创建
- 实例化B时发现需要A,从三级缓存拿到A的ObjectFactory
- 通过getObject()获取A的早期引用(此时A还未初始化完)
- B完成初始化后,A继续完成属性注入
- 最终A和B都进入一级缓存
2.3 为什么需要三级而不是两级?
三级缓存的核心价值在于处理AOP代理场景。如果只有两级缓存:
- 当存在AOP切面时,从二级缓存直接拿到的是原始对象
- 后续生成代理对象后,会导致容器中存在原始对象和代理对象两个实例
通过ObjectFactory的getObject()动态决定返回原始对象还是代理对象,确保整个生命周期中对象的唯一性。
3. 实战中的典型问题与解决方案
3.1 构造器注入循环依赖报错
错误示例:
java复制@
