为什么Spring解决循环依赖需要三级缓存?二级不够吗?

文章目录一、三级缓存的核心组成二、为什么二级缓存不够?1. 初步设想:二级缓存方案2. AOP代理问题的引入3. 三级缓存如何解决这个问题三、三级缓存的工作流程详解四、为什么不能只用二级缓存&am

文章目录

    • 一、三级缓存的核心组成
    • 二、为什么二级缓存不够?
      • 1. 初步设想:二级缓存方案
      • 2. AOP代理问题的引入
      • 3. 三级缓存如何解决这个问题
    • 三、三级缓存的工作流程详解
    • 四、为什么不能只用二级缓存?
    • 五、性能考量
    • 六、源码佐证
    • 七、总结

Spring解决循环依赖的三级缓存机制是一个精妙的设计,很多开发者会有疑问:为什么需要三级缓存?二级缓存看起来也能解决问题啊?让我们深入分析这个问题。

一、三级缓存的核心组成

首先回顾一下Spring的三级缓存结构:

  1. 一级缓存(singletonObjects):存放完全初始化好的Bean
  2. 二级缓存(earlySingletonObjects):存放原始的Bean对象(尚未填充属性)
  3. 三级缓存(singletonFactories):存放Bean的工厂对象(ObjectFactory)

二、为什么二级缓存不够?

1. 初步设想:二级缓存方案

假设我们只有二级缓存,处理流程可能是这样的:

  • 创建Bean A时,实例化后直接放入二级缓存
  • 填充属性时发现需要Bean B
  • 创建Bean B时,实例化后放入二级缓存
  • Bean B需要注入Bean A,从二级缓存中获取
  • 完成Bean B的初始化
  • 完成Bean A的初始化

看起来似乎也能工作,那为什么Spring不这样做呢?

2. AOP代理问题的引入

关键问题在于Spring AOP代理。如果Bean需要被代理(如使用了@Transactional等AOP增强),简单的二级缓存方案就无法正确处理了。

考虑以下场景:

@Service
@Transactional
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
@Transactional
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

在这种情况下:

  1. 如果直接将原始对象放入二级缓存,后续AOP无法介入
  2. 如果直接将代理对象放入二级缓存,此时Bean还未初始化完成,代理可能不完整

3. 三级缓存如何解决这个问题

三级缓存存储的是ObjectFactory,它可以在需要时决定返回原始对象还是代理对象:

// AbstractAutowireCapableBeanFactory
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;
}

这段代码允许后处理器(如AOP代理创建器)在需要时返回代理对象。

三、三级缓存的工作流程详解

让我们通过一个需要AOP代理的例子,看看三级缓存如何工作:

  1. 创建Bean A

    • 实例化A对象(原始对象)
    • 将A的ObjectFactory放入三级缓存
      singletonFactories.put(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
      
  2. 填充A的属性,发现需要Bean B

    • 开始创建Bean B
  3. 创建Bean B

    • 实例化B对象(原始对象)
    • 将B的ObjectFactory放入三级缓存
    • 填充B的属性,发现需要Bean A
  4. 解决Bean A的依赖

    • 从三级缓存获取ObjectFactory
    • 调用getEarlyBeanReference()方法
      • 如果A需要代理,这里会返回代理对象
      • 否则返回原始对象
    • 将结果放入二级缓存,并从三级缓存移除
  5. 完成Bean B的初始化

    • 将初始化好的B放入一级缓存
  6. 完成Bean A的初始化

    • 如果A被代理了,这里要确保返回的是代理对象
    • 将最终结果放入一级缓存

四、为什么不能只用二级缓存?

如果只用二级缓存:

  1. 无法处理AOP代理:要么过早创建代理(可能不完整),要么无法创建代理
  2. 一致性难以保证:在循环依赖解决过程中,可能得到不一致的Bean引用(原始对象或代理对象混用)
  3. 扩展性受限:无法灵活应对各种BeanPostProcessor的处理需求

五、性能考量

三级缓存看起来增加了复杂度,但实际上:

  1. 内存占用小:ObjectFactory是轻量级的
  2. 按需创建:只有发生循环依赖时才会调用ObjectFactory
  3. 生命周期清晰:Bean在不同阶段明确处于不同缓存中

六、源码佐证

DefaultSingletonBeanRegistry中可以看到明确的处理逻辑:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 首先检查一级缓存
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 然后检查二级缓存
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 最后从三级缓存获取ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

这段代码清晰地展示了三级缓存的查询顺序和处理逻辑。

七、总结

Spring采用三级缓存而非二级缓存的主要原因包括:

  1. 支持AOP代理:三级缓存通过ObjectFactory延迟决定返回原始对象还是代理对象
  2. 生命周期管理:明确区分不同阶段的Bean(原始对象、早期引用、完整Bean)
  3. 扩展性:允许各种BeanPostProcessor介入Bean的创建过程
  4. 一致性保证:确保在整个依赖注入过程中Bean引用的一致性

因此,三级缓存不是过度设计,而是Spring为了解决包括AOP代理在内的复杂循环依赖场景所必需的设计。这种机制在保证功能完整性的同时,也兼顾了性能和扩展性。

发布者:admin,转转请注明出处:http://www.yc00.com/web/1754940651a5218088.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信