写点什么

Spring 到底是如何解决循环依赖问题的?

作者:高端章鱼哥
  • 2023-12-04
    福建
  • 本文字数:2143 字

    阅读完需:约 7 分钟

Spring 作为当前使用最广泛的框架之一,其重要性不言而喻。所以充分理解 Spring 的底层实现原理对于咱们 Java 程序员来说至关重要,那么今天笔者就详细说说 Spring 框架中一个核心技术点:如何解决循环依赖问题?

什么是循环依赖问题?


Spring 的循环依赖问题是指在使用 Spring 容器管理 Bean 的依赖关系时,出现多个 Bean 之间相互依赖,形成一个循环的依赖关系。这意味着 Bean A 依赖于 Bean B,同时 Bean B 也依赖于 Bean A,从而形成一个循环。Spring 容器需要确保这些循环依赖关系被正确解决,以避免初始化 Bean 时出现问题。


如果你去网上搜索“Spring 是如何解决循环依赖问题的”,绝大部分答案都是:Spring 使用三级缓存确保循环依赖的解决,包括"singletonObjects"、"earlySingletonObjects"和"singletonFactories"等缓存,以及占位符的使用等等。这当然没有错,可是看到这些文章的朋友们,你们真的理解了这其中的原理吗?还是只是会背答案呢?那么,今天笔者就来扒一扒 Spring 是如何解决这一问题的底层实现原理。当然要明白这个问题的底层实现原理,你得有一定的 Spring 源码基础才行哦。


现在假设我们有三个类,ClasssA、ClassB、ClassC,代码如下:



下面,我们根据 Spring 关于 Bean 的生命周期管理过程进行分析:


假设首先实例化 ClassA。我们知道在 ClassA 实例化完成后,需要填充属性 classB,在填充 classB 属性之前,会调用 addSingletonFactory 方法,把一个 Lambda 表达式添加到了 singletonFactories 集合中,这个 Lambda 表达式的代码如下:





在填充属性时,需要获取到 classB 的实例对象,也就是说会调用 getBean("classB")来走 classB 这个 bean 实例的生命周期流程。


在获取 classB 实例时,首先会调用 getSingleton 从 singletonObjects 获取(而这个 singletonObjects 就是我们平常所说的单例池, 其实就是个 map 集合):




如果单例池中没有才会去创建,那么此时单例池中肯定没有 ClassB 的实例,所以针对 classB 实例也会走一遍创建实例的生命周期的流程,同样的也会把上述 Lambda 表达式添加到 singletonFactories 集合中。


此时 singletonFactories 集合中就有了 classA 和 classB 的两个表达式。


但是这里我们要特别注意 classB 中需要填充属性 classA,所以在填充 classB 实例的 classA 属性时,同样需要调用 getBean("classA")方法来获取到 classA 的实例,在获取 classA 实例时,同样首先会调用 getSingleton 从单例池中获取:



如代码所示,首先会根据 beanName 从 singletonObjects 获取,也就是获取 classA,很显然,classA 还没有放到单例池里面去,只有完全创建好的实例才会放到单例池里面去。可以看到代码同时执行 isSingletonCurrentlyInCreation,此时这个方法返回的是 true,内容如下:



那这个 isSingletonCurrentlyInCreation 方法是干嘛用的呢?看方法名字就知道了,就是判断当前这个 bean 是否正在创建中,我们在开始创建 classA 的时候就已经把他的名字添加到 singletonsCurrentlyInCreation 这个集合中,表明正在创建 classA。


很显然满足了 if (singletonObject == null &&isSingletonCurrentlyInCreation(beanName))这个条件,于是就进入到 if 的方法体中。


然后从 earlySingletonObjects 这个集合中获取对象,那这个 earlySingletonObjects 又是个啥玩意?只用 singletonFactories 和 singletonObjects 两个缓存集合不就好了吗?还要多此一举使用 earlySingletonObjects 干啥呢?是不是感觉没什么用?千万别这么看,大师们考虑问题比咱们要考虑的周到,不服都不行。


我们这个案例中 ClassA 依赖 ClassB 和 ClassC,ClassB 依赖 ClassA,ClassC 也依赖 ClassA,假如我们没有这个 earlySingletonObjects 会出现什么情况呢?我们调用 singletonFactories.get(beanName)得到前面说的 classA 的那个 Lambda 表达式,然后执行 singletonFactory.getObject()就开始执行这个 Lambda 表达式,在填充 ClassB 中的 classA 属性时是不是相当于执行了这个 Lambda 表达式获取了这个 classA 对象。


好了,到此为止 classA 中的 classB 属性获取到了,接下来填充 classC 了,上述同样的流程,当填充 classC 的 classA 属性时,是不是还得从 singletonFactories 中获取 classA 的 Lambda 表达式,然后再执行那个 Lambda 表达式,于是执行了两次,正常情况下是没有问题的,因为两个 Lambda 表达式返回的结果都是 classA 的实例对象,但是有一种情况下就会有问题了?老铁们此时心中肯定充满疑惑,神马情况呢?


如果执行这个 Lambda 表达式返回的是 classA 的代理对象呢?如果执行了两次,是不是就表明 classB 中的 classA 属性和 classC 中的 classA 属性是两个不同的对象了?这问题可就大了,那么问题又来了,神马情况下会返回 classA 的代理对象?不卖关子了,直接上答案:在 classA 需要 AOP 的情况下,是需要生成代理对象的,而这个生成 AOP 的骚操作就是在这个 Lambda 表达式中实现的,我们下面会详细介绍。


所以这里 Spring 使用了 earlySingletonObjects 这个我们称为二级缓存的集合来暂存下,这样在 classC 填充 classA 属性的时候就不用再次调用 lambda 表达式了,是不是完美的解决了上述的问题?剩下的几行代码很简单,就不多废话了,大家自己看看就知道了。


总结下,Spring 解决循环依赖问题其实就是使用了几个集合类,它们分别是:singletonsCurrentlyInCreation(Set)、singletonFactories(Map)、earlySingletonObjects(Map)、singletonObjects(Map),通过这几个集合的相互配合,最终解决循环依赖问题。

发布于: 刚刚阅读数: 4
用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Spring到底是如何解决循环依赖问题的?_spring_高端章鱼哥_InfoQ写作社区