Spring三级缓存

189 阅读4分钟

今天来学习下Spring的三级缓存机制,主要涉及到以下问题

  • 三级缓存是为了解决什么问题?
  • 二级缓存为什么不行?
  • 三级缓存的实现机制

设计动机

Spring的三级缓存机制,主要是为了解决循环依赖问题,蕴含着对对象创建、依赖注入、代理生成等复杂场景的深度权衡。

  • 当对象A依赖对象B,对象B反向依赖对象A,传统单线程造成死锁困境
  • AOP代理对象在对象初始化完成后,但依赖循环需要提前注入

核心矛盾

  1. 必须在对象初始化后才能确定是否要生成代理对象
  2. 循环依赖需要在初始化前完成暴露对象引用

矛盾推演:

  • 如果提前生成代理:可能错误生成不需要代理的对象代理逻辑依赖初始化后的状态
  • 如果延后生成代理:早期注入的对象与最终对象不一致破坏单例原则

解法演进

三级缓存源码结构

// 核心缓存定义(DefaultSingletonBeanRegistry)
public class DefaultSingletonBeanRegistry ... {
    // 一级缓存:完整 Bean(成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:早期暴露对象(半成品)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 三级缓存:对象工厂(生成半成品的 lambda)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

Bean创建流程与缓存交互

要透彻理解 Spring 的三级缓存机制,我们需要深入源码层面分析其设计哲学。这里以 Spring Framework 5.3.x 的 DefaultSingletonBeanRegistry 源码为核心进行解析。

一、三级缓存源码架构

// 核心缓存定义(DefaultSingletonBeanRegistry)
public class DefaultSingletonBeanRegistry ... {
    // 一级缓存:完整 Bean(成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:早期暴露对象(半成品)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 三级缓存:对象工厂(生成半成品的 lambda)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

缓存访问核心方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 第一级缓存查询(已完全初始化的 Bean)
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 第二级缓存查询(已暴露但未初始化的 Bean)
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 第三级缓存查询(对象工厂)
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 关键操作:通过工厂获取早期引用
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

二、Bean 创建流程与缓存交互 典型场景:Bean A → 依赖 → Bean B → 依赖 → Bean A 创建 Bean A

protected Object doCreateBean(...) {
    // 1. 实例化(此时对象是原始状态)
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();

    // 2. 将对象工厂放入三级缓存(关键!)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

    // 3. 属性填充(触发依赖解析)
    populateBean(beanName, mbd, instanceWrapper);
}

解析依赖 Bean B触发 getBean(B) → 重复上述流程创建 Bean B 当 Bean B 需要注入 Bean A 时:

// 在 getSingleton(A) 时:
ObjectFactory<?> factory = singletonFactories.get("A");
Object earlyA = factory.getObject(); // 触发 SmartInstantiationAwareBeanPostProcessor

代理对象生成时机

protected Object getEarlyBeanReference(...) {
    Object exposedObject = bean;
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            // 这里会生成 AOP 代理(如有必要)
            exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp)
                .getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

三、为什么二级缓存不够? 假设只有二级缓存(删除三级缓存): // 伪代码示意:

public Object getSingleton(String beanName) {
    Object bean = singletonObjects.get(beanName);
    if (bean == null) {
        bean = earlySingletonObjects.get(beanName); // 直接取二级缓存
        if (bean == null) {
            bean = createBean(...); // 直接创建新实例
            earlySingletonObjects.put(beanName, bean);
        }
    }
    return bean;
}

会导致两个致命问题:

  1. 代理对象重复创建 2. 当Bean需要AOP代理时,第一次是从二级缓存中获取原始对象,后续初始化时生成代理对象,导致内存中存在两个对象

2.并发场景下的状态不一致

// 线程1:正在创建 Bean A
// 线程2:同时请求 Bean A
if (bean == null) {
    // 两个线程都可能进入创建流程
    bean = createBean(...);
}

四、三级缓存如何解决这些问题?

  1. 代理对象单例保证 通过 ObjectFactory 延迟代理生成:
singletonFactories.put(beanName, () -> {
    // 确保在此刻统一处理代理逻辑
    return getEarlyBeanReference(beanName, mbd, bean);
});

无论有多少个依赖方,只通过工厂生成一次代理 2. 创建过程原子性 三级缓存到二级缓存的转移是同步操作:

synchronized (this.singletonObjects) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}
  1. 对象状态追踪 三个缓存明确区分生命周期阶段:

三级缓存:已实例化,未初始化,可生成早期引用 二级缓存:已生成早期引用,正在初始化 一级缓存:完全初始化完成

代价权衡

  1. 内存开销:每个 Bean 在创建过程中需要额外存储 ObjectFactory(约 40-60 bytes)
  2. 并发性能:全局 synchronized 块导致创建吞吐量下降
  3. 复杂度提升:缓存状态转移逻辑增加了调试难度

总结

三级缓存主要是为了解决循环依赖与AOP代理对象之间的矛盾点,之所以二级缓存不够是因为无法保证对象单例性的同时,处理对象生成时序的问题