什么是 Spring 的三级缓存?

#Java#面试八股

什么是 Spring 的三级缓存?

结论先行:三级缓存是 Spring 为解决单例 Bean 的 setter/字段注入循环依赖而在 DefaultSingletonBeanRegistry 中维护的三个 Map:singletonObjects(一级,成品)、earlySingletonObjects(二级,早期引用)、singletonFactories(三级,对象工厂)。核心手法是"提前暴露引用":Bean 还没初始化完,就先把它的引用(必要时是代理)交出去,让依赖方先完成装配。三级而非两级,是为了让 AOP 代理延迟生成且只生成一次。

一、三个 Map

级别字段存什么何时放入何时移除
一级singletonObjects完全初始化好的单例addSingleton(初始化完成后)一般不删,容器销毁时清
二级earlySingletonObjects已曝光但未初始化的早期引用(可能是代理)getSingleton 从三级取出后放入一级时移除
三级singletonFactoriesObjectFactory,调用 getEarlyBeanReferencedoCreateBean 属性填充前被二级取走后移除

二、时序

① getBean(A)
     └─ doCreateBean(A):new A(),属性填充发现依赖 B
② getBean(B)
     └─ doCreateBean(B):new B(),属性填充发现依赖 A
③ getSingleton(A, true)
     一级无 → 二级无 → 三级有 A 的 ObjectFactory
     └─ factory.getEarlyBeanReference() → 可能返回 A 的代理
        └─ 放入二级,移除三级
④ B 拿到 A 的早期引用,完成初始化 → 放入一级
⑤ A 拿到 B,完成属性填充与初始化
     └─ 最终放入一级时,若二级里是代理则直接复用,避免重复代理

三、关键源码骨架

// 1. 创建 Bean 时,属性填充之前把 ObjectFactory 放进三级缓存
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {
    Object bean = createBeanInstance(mbd, beanName, args);   // 实例化
    boolean earlyExposure = mbd.isSingleton() && this.allowCircularReferences
            && isSingletonCurrentlyInCreation(beanName);
    if (earlyExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    populateBean(beanName, mbd, bean);                       // 属性填充(可能触发循环)
    Object exposed = initializeBean(beanName, bean, mbd);    // 初始化 + AOP 代理
    addSingleton(beanName, exposed);
    return exposed;
}

// 2. 取单例:依次查三级 → 二级 → 一级
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singleton = singletonObjects.get(beanName);          // 一级
    if (singleton == null && isSingletonCurrentlyInCreation(beanName)) {
        singleton = earlySingletonObjects.get(beanName);        // 二级
        if (singleton == null && allowEarlyReference) {
            ObjectFactory<?> f = singletonFactories.get(beanName);  // 三级
            if (f != null) {
                singleton = f.getObject();                      // 生成/取代理
                earlySingletonObjects.put(beanName, singleton);
                singletonFactories.remove(beanName);
            }
        }
    }
    return singleton;
}
// 提前暴露的前提:该单例"正在创建中"
protected boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}
// beforeSingletonCreation 加入、afterSingletonCreation 移除,
// 所以正常 Bean 从不走 earlyReference 分支,三级缓存几乎零成本。

四、为什么必须是三级

  • 如果只有一级:未初始化的对象会污染缓存,别处拿到半成品。
  • 如果只有两级(成品 + 早期引用):早期引用必须在"实例化后立刻"生成,此时 BeanPostProcessor 还没跑,AOP 代理无从生成;若等到需要时再生成,又可能被多次调用产生多个代理。
  • 三级的价值:放的是 ObjectFactory 而不是实例,按需调用。正常无循环的 Bean 从不调用它,代理只在真正发生循环时才提前生成,且同一 Bean 只生成一次(二级缓存去重)。

五、常见追问

  • 问:为什么构造器注入的循环依赖解决不了? 构造器注入要求先有依赖实例才能 new,实例还没产生,无从"提前暴露引用"。
  • 问:prototype 作用域能解决吗? 不能。容器不缓存 prototype,没有三级缓存可用,直接抛 BeanCurrentlyInCreationException。
  • 问:@Async 代理的 Bean 循环依赖为何失败? @Async 代理由 AsyncAnnotationBeanPostProcessor 在初始化后创建,而三级缓存暴露的是初始化前的引用,两边可能生成不同对象,Spring 会检测到并报错。
  • 问:Spring Boot 2.6 之后为什么默认禁止循环依赖? spring.main.allow-circular-references=false,鼓励用分层重构、@Lazy、事件解耦替代隐式循环。

小结

三级缓存 = 成品缓存 + 早期引用缓存 + 对象工厂缓存,本质是"用延迟生成换取代理正确性"。答题顺序:三张 Map 各存什么 → 时序 → 为什么三级 → 哪些场景救不了。把"三级是为了 AOP 代理"这句话说出来,就和只背八股的答案拉开差距了。

笔记加载中…