Spring 循环依赖 - 揭开源码原来没那么神秘

Spring 循环依赖源码解析

前言

自己也经常看Spring源码,但是当初看的时候没有太过关注Spring 对于循环依赖这部分的解决方法,偶然看到网上几篇讲解的Spring 怎么解决循环依赖的解释,感觉自己还是没看太懂,所以自己动手探索下Spring 怎么解决的。

问题

首先我们需要知道什么叫做循环依赖,它是怎么产生的?
其实产生的原因是怪我们太懒了,靠Spring做了很多事情,包括对象的创建,对象的注入(IOC,DI),以至于帮我们注入的时候,发现实例A需要引用对象B的实例,但是实例B里又引用对象A的实例,导致了A ->B ->A 的环形套娃。

在这里插入图片描述

产生方式

关于Spring 依赖注入的方式,大家可以百度谷歌。这里只说结论。
1、构造器循环依赖,spring不能解决;2、setter循环依赖,spring可以解决。

看源码

一、获取A对象
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

Bean对象的获取基本上都是通过这个方法,我们从这里开始探寻Bean对象 A 的获取及实例化。
在这里插入图片描述

这个方法里先通过
Object sharedInstance = getSingleton(beanName);

看能否从这里面得到一个对象,如果得到的话检查一下就强转返回,如果得到的是Null的就走上面 else 创建。我们看下这个方法里干了啥。
在这里插入图片描述
在这里插入图片描述
重点介绍下:我们看到了,它引入了三个东西,被称作三级缓存。

singletonObjects:第一级缓存已经实例化完成的对象。
earlySingletonObjects:第二级缓存存放的是早期曝光的单例对象。
singletonFactories :第三级缓存存放的是单例的工厂对象缓存。

上面三级缓存的新增和删除,接下来字体都会加粗。

在这个方法里,依次判断对象是否存在在里面,最后一个判断,如果在 singletonFactories 找不到这个对象,就返回Null,否则就把找到的对象 put 进 earlySingletonObjects 中,并从singletonFactories 中移除。(注:这步就是把三级缓存中第三级缓存转移到第二级缓存中,通过 getSingleton 顺便做这些事情)

我们这刚开始创建所以这里面肯定啥都没,所以这里返回Null,返回到 doGetBean 方法里的 else 部分了去创建 Bean A 了。

在这里插入图片描述
1):第一步那里会执行A的构造器函数,返回对象A的引用。

2):第二步那里会把A对象引用放进 singletonFactories 中,这步就是 singletonFactories 缓存中东西的来源;执行完构造器后就做的操作。
3):第三步里面会解析A的依赖,并注入依赖的对象。这里是解析到A 依赖 B,然后通过 doGetBean 得到 B对象 。看到这个doGetBean 是不是很熟悉,这个创建A对象的开头。

二、获取B对象

上一部分通过创建A对象,并注入依赖关系时需要得到B对象,接下来我们走得到B对象的流程。
B 对象还是通过

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

这个来创建的,重复上面A对象创建流程一样,依次1,2,3。并在第二步加入到三级缓存中的 singletonFactories 。
最后对象B也会在第三步去解析依赖关系,去注入A对象,但是此时A对象还没有完全创建完成,还在创建它的依赖对象B流程呢。

三、再次获取A对象
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

再次通过这个获取A对象。
在这里插入图片描述
此时这个 Object sharedInstance = getSingleton(beanName); 的走向和第一次获取A对象时的走向就不一样了。因为singletonFactories 里面已经有了缓存了A对象的引用了(就是A被执行完构造器之后放进去了的,上面解释了)。
在这里插入图片描述
把A 移到 earlySingletonObjects 缓存中,把A 从singletonFactories 缓存中移除,并直接返回这个对象的引用。

四、继续B对象的创建

刚刚咱们上面进行到了B对象解析依赖关系去获取A对象,现在A对象已经获取到了,继续进行B对象的创建,继续执行第三步后面的操作
在这里插入图片描述

4):这是对象创建的第四步时,前面几步已经完成对象的构造器、注入、实例化,此时把对象B放进 earlySingletonObjects 缓存中,把B 从singletonFactories 缓存中移除,并直接返回这个对象的引用,此时已成功创建一个新的单例对象,接下来就会执行第五步。
在这里插入图片描述
5):这个方法里会把B 对象从第二级和第三级缓存都移除掉,放到第一级缓存中。
在这里插入图片描述
第五步执行完后就会来到咱们熟悉的每个对象获取的开头部分。

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

在这里插入图片描述
执行第六步返回获取到的对象 B。
到此,Bean对象B 已经注入对象A并已完成实例化,已经是个完整的对象了。对象B 已经被挪到第一级缓存中了,已经不存在在另外两个缓存中了。
现在咱们继续回到对象A 的创建流程中。

五、继续A对象的创建

此时对象A 已经在 earlySingletonObjects 缓存中,在上面 ( 三、)中已经完成这个操作了,可以往上翻下。
所以上述截图中 4 号方法就不会再对 A 进行缓存挪动操作了,get到了直接返回 A。
继续执行来到了上述截图中 5 号方法,把A 对象从第二级和第三级缓存都移除掉,放到第一级缓存中。
继续执行来到了上述截图中 6 号方法,返回获取到的对象 A。
至此,单例对象Bean A和 单例对象Bean B 已完成所有实例化操作。

结语

循环依赖的破环,主要依靠第三级缓存 singletonFactories,它先存储了对象A 的引用,虽然A对象此时还没实例化完成,但是已经开辟了内存空间,通过它终止了这个流程。

开篇提到构造器循环无法Spring无法解决,通过第三级缓存咱们应该可以知道了,Bean对象会在执行完构造器之后才会把引用放进第三级缓存中。

关于这边文章因为Spring里的东西太多了,我只能关注咱们需要解决的问题,着重点进行讲解,后面如果找到一个比较好画时序图的软件,应该会画一个时序图。

大家如果有疑问,欢迎留言,我看到的话抽空会回复的。

不好意思,这篇文章禁止转载,毕竟我写了这么多 😹。大家可以多多点赞🤚藏,谢谢大家。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值