【Spring源码三千问】Spring 是怎样解决循环依赖问题的?三级缓存的作用是什么?

前言

大家都知道 Spring 解决了循环依赖的问题,网上也可以搜到 Spring 是使用三级缓存来解决循环依赖的。
那大家思考过没有,Spring 为什么要用三级缓存来解决循环依赖问题?使用两级缓存行不行?只使用一级缓存行不行?
哪些循环依赖场景是 Spring 没办法解决的?
本文先分析一下, Spring 是如何通过三级缓存来解决循环依赖问题的。

三级缓存的作用是 Spring IoC 的难点,搞清楚它的原理和背后的原因非常有必要!

版本约定

Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)

正文

前面我们在分析bean 的创建过程时讲到,bean 的创建是由 AbstractBeanFactory#getBean() 触发的。
跟一下 AbstractBeanFactory#getBean(java.lang.String) 的源码,会发现它会调用 DefaultSingletonBeanRegistry#getSingleton()

// DefaultSingletonBeanRegistry#getSingleton()
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    // 一级缓存中没有 beanName, 且 beanName 正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        // bean 的早期引用中没有 beanName,且允许早期引用
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.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();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

以上就是三级缓存的代码。
那我们就来好好研究一下,它是如何来解决循环依赖的!

三级缓存的定义如下:

// DefaultSingletonBeanRegistry.java

/** 一级缓存: Cache of singleton objects: bean name to bean instance. */
// 用于存放已经完全初始化好的 bean 
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 三级缓存: Cache of singleton factories: bean name to ObjectFactory. */
// 用于存放 bean 的早期引用(都会存放,循环依赖时才会使用)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** 二级缓存: Cache of early singleton objects: bean name to bean instance. */
// 用于存放三级缓存 ObjectFactory 获取到的对象(循环依赖时才会存放并使用)  
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

准备例子

我们准备一个最简单的循环依赖的例子,A --> A 的场景:

@Service
public class AService {
    @Autowired
    AService aService;
    
    public AService getaService() {
        return aService;
    }
}

public class CircleTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.kvn.beans.circle");
        AService a = applicationContext.getBean(AService.class);
        System.out.println(a + "--" + a.getaService());
    }
}

程序可以正常运行,且 AService 中注入的 AService 是它自身。
这也就证明了 Spring 是解决了循环依赖的问题的。

循环依赖过程分析

AbstractBeanFactory#getBean() 在触发 bean 的加载时,会先从缓存中获取 bean,也就是会调用 DefaultSingletonBeanRegistry#getSingleton() 方法。

通过上面最简的例子调试 DefaultSingletonBeanRegistry#getSingleton(),会发现:
AService 初始化过程中,第一次调用 getSingleton() 时,一级缓存 singletonObjects 中没有 AService,且 bean 也没有在创建中。
后面在 createBean() 创建 AService 的实例时,会将创建出的 bean 实例引用通过 ObjectFactory 放到三级缓存 singletonFactories 中进行暴露。
populateBean() 注入依赖时,会 第二次调用 getSingleton() ,此时 AService 已经在创建中,且已经存在 AService 对应的三级缓存了,通过三级缓存 ObjectFactory#getObject() 就可以获取到 bean 的早期引用 。
这时,就可以注入进去了。

事实上,第一次调用 getSingleton() 时,三个缓存中都没有 AService
三级缓存 singletonFactories 中暴露的是一个 ObjectFactory 对象,通过 ObjectFactory#getObject() 可以获取到 AService bean 对应的早期引用。

第二次调用 getSingleton() 时的断点图:
L3Cache

bean 在创建时,缓存的使用情况:
circleRefer

结合 bean 的创建过程来分析一下缓存的使用情况

bean 的创建过程分三个阶段:
1 创建实例 createBeanInstance
2 填充依赖 populateBean
3 initializeBean

下图是 bean 的创建流程:
doCreateBean2

  • 在没有循环依赖的情况下
    新创建一个 bean 实例后,会先将 bean 对应的 ObjectFactory 暴露到三级缓存 singletonFactories 中。
    bean 初始化完成后,会清除掉相应的二级缓存、三级缓存,同时,将初始化完成的 bean 放入一级缓存 singletonObjects 中。
    createBeanSequence

  • 在有循环依赖的情况下:(A --> B --> A 的场景)
    A 第一次加载时,会将 A 对应的 ObjectFactory 放到三级缓存中;
    当 B 创建完实例后,进行 populateBean 填充依赖时,会通过 getBean(A) 来获取 bean A,这时会使用 A 对应的三级缓存 ObjectFactory 来获取 bean A 的早期引用。

circleSequence

三级缓存的作用是什么?

在没有循环依赖的情况下,第三级缓存只是在创建 bean 的时候单纯的存储在了 Map<String, ObjectFactory<?>> singletonFactories 中,并没有进行使用。
所以,在没有循环依赖的情况下,其实是不需要三级缓存、二级缓存的,只用一个一级缓存就可以搞定了。

那在有循环依赖的情况下,为什么不用三级缓存就搞不定呢?

第三级缓存在添加时,是通过 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 添加到 singletonFactories 中的。
当 bean 被循环依赖时,第一级缓存 singletonObjects 中还没有完全创建好的 bean;此时,第二级缓存 earlySingletonObjects 中也没有 bean 的早期引用;
所以,这时只能通过第三级缓存 singletonFactories 来获取 bean 的早期引用。

第三级缓存对应的 ObjectFactory 的实现是通过 lambda 表达式: () -> getEarlyBeanReference(beanName, mbd, bean) 来实现的,具体代码如下:

// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()  
/**
 * Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
 * 获取指定 bean 的早期引用,通常用于解析循环引用。
 */
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);
        }
    }
    return exposedObject;
}

可以看到,第三级缓存会将 bean 的原始引用通过 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference() 来进行处理。
它只有一个实现,如下:

// AbstractAutoProxyCreator#getEarlyBeanReference() 
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 如果有需要的话,对 bean 生成代理
    return wrapIfNecessary(bean, beanName, cacheKey);
}

如果 bean 是需要被 AOP 增强的话,getEarlyBeanReference() 就会提前给 bean 的原始引用生成代理。
如果 bean 不需要被 AOP 增强的话,getEarlyBeanReference() 就不会做任何操作,直接返回原始引用。

所以,第三级缓存真正起作用的是针对 AOP 代理 bean 被循环引用的情况,这种 bean 需要提前通过第三级缓存生成 AOP 代理对象,然后放入二级缓存中。

为什么需要被 AOP 增强的 bean 需要提前生成 AOP 代理类呢?

那么,问题来了:为什么需要被 AOP 增强的 bean 需要提前生成 AOP 代理类呢?
首先,class A 的原始 bean 和 A 的代理 bean 肯定是不同的对象引用。
设想一下:假设 A --> B --> A, A 是需要被 AOP 增强的 bean 。
如果 B 在进行依赖注入时,注入的是 A 的原始 bean,而不是代理 bean 的话,那么 B 中持有的 A 没有被 AOP 增强过,那么就达不到我们预期的效果,就是有问题的。

所以,如果没有三级缓存的话,那么,对于 AOP 代理 bean 的循环依赖就解决不了。这就是三级缓存存在的核心意义!!!

小结

Spring 通过 三级缓存 为我们解决了循环依赖的问题。
三级缓存的定义和使用情况如下:

/** 一级缓存: 用于存放已经完全初始化好的 bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 二级缓存:用于存放 bean 的早期引用 */ 
// 非循环依赖的场景,不会存放二级缓存
// 在循环依赖的场景下,才会通过三级缓存 ObjectFactory 来获取相应的早期引用对象,放到二级缓存中使用
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** 三级缓存: 用于存放 bean 对应的 ObjectFactory */
// 不管是否有循环依赖,在 createBeanInstance 之后都会存放三级缓存
// 循环依赖时,才会使用到三级缓存,通过三级缓存来获取 bean 的早期引用对象,放到二级缓存中,同时删除掉三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

三级缓存的作用:
三级缓存是为了解决循环依赖的问题,更确切的说: 三级缓存是为了解决 AOP 代理 bean 被循环依赖的场景。
AOP 代理 bean 被循环依赖时,需要提前通过第三级缓存生成 AOP 代理对象,然后放入二级缓存中,这样就能保证最终被注入的依赖 bean 就是 Spring 最终暴露到容器中的 bean。

换句话说:当 AOP 代理 bean 被循环依赖时,需要通过第三级缓存 singletonFactories 提前获取到原始 bean 对应的 AOP 代理对象,从而将 AOP 代理 bean 的引用作为依赖注入到目标对象中。

思考:
既然 Spring 为我们解决了循环依赖的问题,但是我们有时还是会碰到循环依赖的报错:Is there an unresolvable circular reference?
那么,什么情况下 Spring 解决不了循环依赖的问题呢?

了解更多,请戳: 【Spring源码三千问】哪些循环依赖问题Spring解决不了?


SpringIoC源码视频讲解:

课程地址
SpringIoC源码解读由浅入深https://edu.51cto.com/sd/68e86

如果本文对你有所帮助,欢迎点赞收藏!
有关 Spring 源码方面的问题欢迎一起交流,备注:CSDN (vx: Kevin-Wang001)

阅读更多文章,请关注公众号: 老王学源码
gzh


老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载

公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…


系列博文:
【老王读Spring IoC-0】Spring IoC 引入
【老王读Spring IoC-1】IoC 之控制反转引入
【老王读Spring IoC-2】IoC 之 BeanDefinition 扫描注册
【老王读Spring IoC-3】Spring bean 的创建过程
【老王读Spring IoC-4】IoC 之依赖注入原理
【老王读Spring IoC-5】Spring IoC 小结——控制反转、依赖注入

相关阅读:
【Spring源码三千问】@Resource 与 @Autowired 的区别
【Spring源码三千问】bean name 的生成规则
【Spring源码三千问】BeanDefinition详细分析
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Spring框架中,三级缓存是用于解决循环依赖问题的一种机制。三级缓存分别是singletonObjects、earlySingletonObjects和singletonFactories。 1. singletonObjects:该缓存用于存储完全初始化完成的单例对象。在对象创建完成后,会将其放入此缓存中。 2. earlySingletonObjects:该缓存用于存储早期曝光的未完全初始化的单例对象。在对象的创建过程中,如果发生循环依赖,会将正在创建的对象放入此缓存中。 3. singletonFactories:该缓存用于存储用于创建单例对象的工厂对象。当对象创建过程中发生循环依赖时,会将正在创建对象的工厂对象放入此缓存中。 这三个Map在Spring中的异同如下: - 异同点:它们都是用于缓存单例对象的,用于解决循环依赖问题。 - 不同点:singletonObjects用于存储完全初始化完成的单例对象,而earlySingletonObjects和singletonFactories则分别用于存储未完全初始化的单例对象和创建单例对象的工厂对象。 循环依赖是指两个或多个Bean之间相互依赖形成了一个闭环,无法正确地创建这些Bean实例。例如,A依赖B,B又依赖A,就形成了循环依赖。在Spring中,循环依赖是一种容器级别的问题。 我了解Spring源码中的循环依赖处理机制,Spring通过使用三级缓存和提前曝光的方式来解决循环依赖问题。在对象创建过程中,当检测到循环依赖时,会从缓存中获取早期曝光的对象,从而避免了循环依赖带来的问题。 检测是否存在循环依赖可以通过构造器循环依赖来实现,当一个Bean在创建过程中多次被构造器引用,则说明存在循环依赖Spring会抛出BeanCurrentlyInCreationException异常来表示循环依赖的存在。 在实际开发中,循环依赖是一个常见的问题。当Bean之间的依赖关系设计不当或者配置错误时,就可能出现循环依赖的异常。在这种情况下,我们需要仔细检查Bean的依赖关系,并进行适当的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

老王学源码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值