Spring源码之解决循环依赖 三级缓存

目录

三级缓存核心原理

循环依赖的解决过程

1. Bean A创建过程中提前曝光工厂

2. Bean B创建时发现依赖A,从缓存获取

3. Bean A继续完成初始化

三级缓存的作用总结

二级缓存为何不够解决缓存依赖?

三级缓存如何解决?

为什么不直接在实例化后创建代理?

总结


Spring通过三级缓存机制解决循环依赖问题,这与populateBean()方法密切相关。下面我用简化代码解释这个机制:

三级缓存核心原理

Spring在DefaultSingletonBeanRegistry中维护了三个重要的缓存:

// 一级缓存:存储完全初始化的单例bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存储早期曝光的单例bean(未完全初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 三级缓存:存储单例工厂对象,用于创建早期曝光的bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

循环依赖的解决过程

当发生循环依赖时,Spring通过以下步骤解决:

1. Bean A创建过程中提前曝光工厂
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // 1. 实例化Bean(调用构造函数)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    // 2. 提前曝光一个ObjectFactory,用于解决循环依赖
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences);
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    
    // 3. 填充属性(可能触发对Bean B的依赖)
    populateBean(beanName, mbd, instanceWrapper);
    
    // 4. 初始化Bean(调用init方法和AOP代理)
    exposedObject = initializeBean(beanName, exposedObject, mbd);
    
    return exposedObject;
}

// 将工厂添加到三级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
        }
    }
}
2. Bean B创建时发现依赖A,从缓存获取

populateBean()处理B的依赖时:

protected Object resolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
    
    // 查找依赖的bean(假设这里是A)
    String dependencyName = descriptor.getDependencyName();
    
    // 从三级缓存中获取bean
    Object bean = getSingleton(dependencyName, false);
    
    return bean;
}

// 从三级缓存中获取bean的核心方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    
    // 2. 如果一级缓存没有且bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 3. 从二级缓存获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        // 4. 二级缓存没有且允许早期引用
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // 5. 从三级缓存获取工厂
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 6. 通过工厂创建早期bean实例
                    singletonObject = singletonFactory.getObject();
                    // 7. 放入二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 8. 从三级缓存移除
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}
3. Bean A继续完成初始化

当B完成创建后,A继续完成自己的初始化:

// 完成Bean A的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);

// 将完全初始化的bean放入一级缓存
addSingleton(beanName, exposedObject);

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
    }
}

三级缓存的作用总结

  1. 一级缓存(singletonObjects):存储完全初始化的单例bean,可直接使用
  2. 二级缓存(earlySingletonObjects):存储早期曝光的bean(未完成初始化),用于解决循环依赖
  3. 三级缓存(singletonFactories):存储工厂对象,允许在需要时创建早期bean,并支持AOP代理

通过这种机制,Spring允许在bean尚未完全初始化时就将其引用暴露给依赖它的其他bean,从而解决循环依赖问题。注意:只有单例作用域的bean支持循环依赖,原型模式不支持。你的问题非常关键!实际上,二级缓存理论上可以解决循环依赖,但Spring依然使用三级缓存,主要出于AOP代理的考虑。我来详细解释:

二级缓存为何不够解决缓存依赖?

假设只有两级缓存:

  1. singletonObjects(完全初始化的bean)
  2. earlySingletonObjects(早期曝光的bean)

当出现循环依赖时,我们可以在实例化后直接将bean放入earlySingletonObjects。但这样会有一个问题:如果bean需要AOP代理,早期曝光的将是原始对象,而不是代理对象

举个例子:

// Bean A依赖B,Bean B依赖A
class A {
    @Autowired private B b;
    @Transactional public void doSomething() {} // 需要AOP代理
}

class B {
    @Autowired private A a;
}

当A和B循环依赖时:

  1. A实例化后放入二级缓存(此时是原始对象)
  2. B创建时从二级缓存获取A的原始对象
  3. B完成创建,注入A的原始对象
  4. A继续初始化,创建代理对象

最终结果:B持有的是A的原始对象,而不是代理对象,导致AOP失效!

三级缓存如何解决?

Spring通过三级缓存引入了ObjectFactory,将AOP代理的创建延迟到真正需要早期引用时:

// 三级缓存:存储工厂对象,用于创建可能需要代理的早期bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

// 添加工厂到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

// 获取早期引用的方法(可能创建代理)
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = 
                    (SmartInstantiationAwareBeanPostProcessor) bp;
                // 如果需要代理,这里会创建代理对象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

当B需要A的早期引用时:

  1. 从三级缓存获取ObjectFactory
  2. 通过工厂调用getEarlyBeanReference(),此时才决定是否创建代理
  3. 将结果(可能是代理对象)放入二级缓存

这样一来,B持有的就是A的代理对象,保证了AOP的正确性。

为什么不直接在实例化后创建代理?

你可能会问:为什么不直接在实例化A后就创建代理,然后放入二级缓存?这样不就不需要三级缓存了吗?

原因有两点:

  1. 性能优化:不是所有bean都需要代理,延迟到真正需要时再创建可以避免不必要的代理
  2. 顺序正确性:Spring的后置处理器执行顺序是有规范的。postProcessBeforeInitializationpostProcessAfterInitialization应该在bean初始化阶段执行,而不是实例化阶段。如果提前创建代理,会破坏这个顺序。

总结

二级缓存可以解决普通对象的循环依赖,但无法解决代理对象的循环依赖。三级缓存通过引入ObjectFactory,将代理创建延迟到真正需要早期引用时,既保证了AOP的正确性,又维持了后置处理器的执行顺序。这是Spring在循环依赖和AOP之间找到的精妙平衡点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员多多_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值