循环依赖 - 三级缓存

循环依赖与三级缓存源码分析

循环依赖:A 依赖 B,B 又依赖 A,两边都要求”先有对方才能造好自己”。Spring 的解法是把”实例化”和”属性填充”拆开,对象一 new 出来(还没填属性)就先把一个能拿到它引用的工厂挂出去,靠 DefaultSingletonBeanRegistry 里的三级缓存完成这套”半成品提前暴露”。
它只对「单例 + setter/字段注入」生效;构造器注入在实例化那一刻就要对方,半成品都不存在,无解。

代码块JAVA · 105 行收起展开
// 基于本地 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 里。

代码块JAVA · 75 行收起展开
// 基于本地 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 生成,但代理已经在早期引用那次提前建过了,AbstractAutoProxyCreatorearlyProxyReferences 发现建过,直接返回原对象——这保证全世界只有一个 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 无条件提前建代理,生命周期约定被全面打破——三级换来的是”无循环时零成本”。

延伸阅读