循环依赖 - 三级缓存
循环依赖与三级缓存源码分析
循环依赖:A 依赖 B,B 又依赖 A,两边都要求”先有对方才能造好自己”。Spring 的解法是把”实例化”和”属性填充”拆开,对象一 new 出来(还没填属性)就先把一个能拿到它引用的工厂挂出去,靠 DefaultSingletonBeanRegistry 里的三级缓存完成这套”半成品提前暴露”。
它只对「单例 + setter/字段注入」生效;构造器注入在实例化那一刻就要对方,半成品都不存在,无解。
代码块收起展开
// 基于本地 Spring 仓 (v5.3.39), spring-beans, org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// ...
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 一级:成品单例。运行期 getBean 无锁直达,容量给到 256
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级:存的是"怎么造早期引用"的回调,不是对象。普通 HashMap 就够——读写全程在 singletonObjects 锁内
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // 二级:三级工厂执行一次后的结果,保证早期引用全局唯一
// ...
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16)); // "正在创建中"名单:区分「循环依赖回来了」和「普通的第一次创建」
// ...
// 读取端:三级缓存逐层查。populateBean 注入依赖时最终落到这里
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName); // 一级无锁快路径:容器启动完后所有 getBean 都停在这一行
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 一级没有 && 它正在创建中 => 只可能是循环依赖找回来了
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) { // 全程只有 singletonObjects 这一把锁:三张表之间的状态迁移必须是原子的
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName); // 锁内把一二级重查一遍:进锁前可能有别的线程已把工厂执行掉了
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject(); // 回调 getEarlyBeanReference:此刻才决定给原始对象还是提前生成 AOP 代理
this.earlySingletonObjects.put(beanName, singletonObject); // 结果升二级、删三级:工厂只执行一次 => 代理只创建一次,谁来拿都是同一个引用
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
// 写入端①:实例化后、属性填充前,doCreateBean 把早期引用工厂塞进三级
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory); // 存 lambda 不存对象:要不要提前建代理,推迟到真有人循环依赖时再算
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
// 写入端②:创建完的成品进一级。addSingleton 同时清掉二三级——一个 bean 任一时刻最多存在于一层
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
// 创建入口:doGetBean 里 getSingleton(beanName, () -> createBean(...)) 走的就是这个重载
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// ...
beforeSingletonCreation(beanName); // 创建前先把名字挂进"正在创建中"名单——上面三级查找的前提条件就是它
boolean newSingleton = false;
// ...
try {
singletonObject = singletonFactory.getObject(); // 这里的工厂 = () -> createBean(...),真正进入 doCreateBean
newSingleton = true;
}
// ...
finally {
// ...
afterSingletonCreation(beanName); // 无论成败都摘牌
}
if (newSingleton) {
addSingleton(beanName, singletonObject); // 成品入一级
}
}
return singletonObject;
}
}
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName); // add 返回 false = 同一个 bean 在创建中又被要求创建:构造器循环依赖就在这里炸
}
}
// ...
}三级缓存只是存储,塞与取的时机在 AbstractAutowireCapableBeanFactory.doCreateBean 里。
代码块收起展开
// 基于本地 Spring 仓 (v5.3.39), spring-beans, org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args); // 反射调构造器 new 出裸对象;构造器注入的依赖在这一步就要解析——早期暴露还没发生,所以构造器循环无解
}
Object bean = instanceWrapper.getWrappedInstance();
// ...
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName)); // 三个开关:单例 && 容器允许循环引用 && 确实处于创建中
if (earlySingletonExposure) {
// ...
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 塞三级。lambda 捕获的是裸 bean,被调用时才包装
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper); // 属性注入:依赖别人就在这里触发 getBean(B),循环发生在这条调用链上
exposedObject = initializeBean(beanName, exposedObject, mbd); // init 方法 + BeanPostProcessor;无循环时 AOP 代理在这一步的 after 回调里生成
}
catch (Throwable ex) {
// ...
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false); // allowEarlyReference=false:只查一二级不触发工厂;非 null 说明真有人取走过早期引用
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference; // 初始化没换对象:以别人已注入的那个早期引用为准,保证容器内外同一个实例
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName); // 初始化把对象包了一层,而别人手里还是旧引用:查出受害者名单
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) { // 两个版本并存,宁可启动失败也不留隐患
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
// ...
return exposedObject;
}
// 三级工厂被调用时真正执行的内容:决定早期引用是裸对象还是提前生成的 AOP 代理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); // AbstractAutoProxyCreator 挂在这里:该 bean 要 AOP 就提前建代理并记入 earlyProxyReferences,初始化阶段据此跳过二次建代理
}
}
return exposedObject;
}原理串讲
拿 A、B 互相 setter 注入走一遍。getBean("a") 进 doGetBean,先调 getSingleton("a", true):一级没有,A 也不在创建中名单里,返回 null。
于是走创建重载 getSingleton("a", () -> createBean(...)),第一件事 beforeSingletonCreation("a") 把 “a” 加进 singletonsCurrentlyInCreation,然后回调工厂进入 doCreateBean。
createBeanInstance 反射 new 出裸 A,紧接着 addSingletonFactory("a", () -> getEarlyBeanReference("a", mbd, bean))——注意此刻 A 一个属性都没填,塞进三级的也不是 A 本身,是一个还没执行的 lambda。
populateBean(A) 开始注入属性,发现要 B,触发 getBean("b"),B 走完全一样的流程:挂创建中名单、new 裸 B、塞三级工厂,然后 populateBean(B) 要注入 A——循环闭合点到了。
这次 getSingleton("a", true) 的表现不一样:一级没有,但 isSingletonCurrentlyInCreation("a") 为 true,逐层查到三级命中工厂,执行 getEarlyBeanReference("a", ...)。
如果 A 配了 AOP,AbstractAutoProxyCreator.getEarlyBeanReference 在这里提前把代理建出来并登记进 earlyProxyReferences;没配就原样返回裸 A。
结果升入二级、工厂从三级删除,B 拿着这个早期引用完成注入,接着 initializeBean(B) 走完,addSingleton("b") 让 B 以成品身份进一级。
栈弹回 A 的 populateBean,A 注入到手的已经是成品 B。
A 继续 initializeBean。若 A 有 AOP,正常情况代理应该在这一步的 postProcessAfterInitialization 生成,但代理已经在早期引用那次提前建过了,AbstractAutoProxyCreator 查 earlyProxyReferences 发现建过,直接返回原对象——这保证全世界只有一个 A 的代理。
最后是 doCreateBean 的收尾检查:getSingleton("a", false) 只查一二级,能查到就证明确实有人取走过早期引用;此时若 exposedObject == bean,就把返回值换成那个早期引用,让”注入给 B 的”和”放进容器的”是同一个对象。
如果某个 BeanPostProcessor 在初始化阶段又把 A 包了一层新对象,两个版本对不上,直接抛 BeanCurrentlyInCreationException,报错文案就是上面那段 “injected into other beans in its raw version”。
第一处为什么这么设计——三级为什么存工厂而不直接存对象(二级为什么不够):Spring 的生命周期约定是”代理在初始化之后生成”,若实例化后就无条件把早期对象(或代理)扔进一张半成品表,要么所有 bean 都被迫提前建代理(99% 的 bean 根本没有循环依赖,白白破坏生命周期约定),要么循环场景下 B 注入的是裸 A、容器里最终放的是代理 A,两边不一致。
存工厂把”要不要建代理”这个决定推迟到真的有人循环取用的那一刻,没人取用则工厂从头到尾不执行,无循环的 bean 走的仍是标准生命周期,一点不受影响。
第二处为什么这么设计——工厂执行后为什么要升二级并删三级:工厂不是幂等的,getEarlyBeanReference 每执行一次都可能 new 一个新代理。
如果 C 也依赖 A,C 来取时必须拿到和 B 相同的那个引用,所以第一次执行完就把结果缓存进二级、把工厂废掉,后续访问全部命中二级。
二级的本质就是”工厂的一次性执行结果缓存”,这是单例语义在早期引用阶段的延续。
第三处——构造器循环为什么无解:addSingletonFactory 发生在 createBeanInstance 之后,而构造器注入的依赖解析发生在 createBeanInstance 内部,取用时机早于暴露时机。
A 的构造器要 B,B 的构造器又要 A,getBean("a") 第二次到达 beforeSingletonCreation("a") 时 add 返回 false,当场抛 BeanCurrentlyInCreationException。
破法:改 setter/字段注入,或给构造器参数加 @Lazy 注入一个延迟代理。
相关代理机制见 AOP-动态代理,getBean 主流程见 IOC-refresh。
设计取舍
- 只救「单例 + setter/字段注入」:prototype 每次都要新对象,缓存半成品没有意义;构造器注入取用早于暴露。Spring Boot 2.6 起
allowCircularReferences默认关掉,循环依赖直接启动失败——官方态度是这本来就是设计坏味道,三级缓存是兜底不是鼓励。 - 三张表共用
singletonObjects一把锁(getSingletonMutex()返回的就是它),所以singletonFactories用普通 HashMap 即可;分开加锁反而会让”查一级、升二级、删三级”这类跨表迁移出现中间态。 - 早期引用绕过了完整生命周期:B 拿到的 A 还没执行 init 方法,若 B 在自己的初始化里立刻调用 A 的方法,可能踩到未初始化状态——三级缓存保证的是引用一致,不保证时序完整。
getSingleton(beanName, false)这个收尾自检容易被忽略:三级缓存能提前暴露的只有getEarlyBeanReference认识的包装(AOP 代理),postProcessAfterInitialization里换对象的其他后置处理器一旦撞上循环依赖,靠这道检查兜底报错,而不是静默留下两个版本。- 常被追问”两级行不行”:功能上把工厂执行提前也能跑,代价是所有 AOP bean 无条件提前建代理,生命周期约定被全面打破——三级换来的是”无循环时零成本”。