图解Spring源码之循环依赖


Spring默认在 单例非构造方法注入的情况下是支持循环依赖的。

准备工作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

关闭循环依赖

Spring 提供了关闭循环依赖的方法。

boolean allowCircularReferences

在这里插入图片描述

开始调试

在这里插入图片描述
我们从 doGetBean 开始分析。

AbstractBeanFactory#doGetBean

Spring 在初始化和 getBean() 时,都会调用这个方法。

删减版doGetBean
//... 表示被我省略掉的一些代码
在这里插入图片描述

代码很长,我们节选一些片段来解析

先检查一下单例池当中有没有手动注册的单例对象:
在这里插入图片描述
单例池:
Spring 中所有实例化好的单例 Bean 都存放在这个map当中。
在这里插入图片描述
判断当前的类是否在正在创建的原型集合当中,如果是,则抛异常:
在这里插入图片描述
说明一般情况下,原型不支持循环依赖。

prototypesCurrentlyInCreation:
在这里插入图片描述
判断当前 Bean 是否是单例,如果是,则把 Bean 创建出来。
在这里插入图片描述

getSingleton(String beanName, ObjectFactory<?> singletonFactory)

在这里插入图片描述
分析与循环依赖有关的代码片段:

判断当前实例化的 Bean 是否在正在被销毁:
在这里插入图片描述
singletonsCurrentlyInDestruction:
在这里插入图片描述
beforeSingletonCreation(beanName):

判断当前 Bean 是否正在被创建。因为 Spring 不管创建 Prototype Bean 还是 Singleton Bean,当他需要正式创建 Bean 的时候他会将这个 Bean add 到一个Set 中去
在这里插入图片描述

  • this.inCreationCheckExclusions.contains(beanName)判断当前需要创建的 Bean 是否在 Exclusions 集合中,程序员可以提供一些 Bean 不被 Spring 初始化(哪怕被扫描到了,也不初始化),那么这些被提供的 Bean 便会存在这个集合当中;
  • this.singletonsCurrentlyInCreation.add(beanName)把当前正在创建的 Bean 添加到正在创建的集合中。
    在这里插入图片描述

singletonsCurrentlyInCreation:
在这里插入图片描述
继续往下执行到singletonObject = singletonFactory.getObject();
在这里插入图片描述
这里的 singletonFactory 就是上面传进来的那个 Lamda 表达式,我再贴一下:
在这里插入图片描述
所以我们要进到 createBean 方法中去。

从调用栈中我们也可以看出来这点:
在这里插入图片描述

AbstractAutowireCapableBeanFactory#createBean(beanName, mbd, args)

在这里插入图片描述
这里主要是 doCreateBean 方法:在这里插入图片描述
调用 doCreateBean 后,x 和 y 都完成实例化了(循环依赖也完成了):
在这里插入图片描述

doCreateBean

下面着重分析一下 doCreateBean 这个方法,上面我们分析过一个名字类似的叫 doGetBean ,别搞混了:
在这里插入图片描述
createBeanInstance(beanName, mbd, args):
在这里插入图片描述
运行完以后,x 的构造方法就被调用了:
但是此时 x 对象只是被创建了,它还不是 Spring Bean,它的生命周期才刚刚开始。
在这里插入图片描述
往下执行到 earlySingletonExposure 变量赋值处:
在这里插入图片描述

  • mbd.isSingleton() 我们的 Bean 都是单例的,所以此处为 true
  • this.allowCircularReferences 默认允许循环依赖,此处为 true
  • isSingletonCurrentlyInCreation(beanName) x 在正在创建的 Bean 的集合中,此处为 true

这里又印证了上面我们说的关闭循环依赖的方法。

继续向下执行,这里又是一个 Lamda 表达式:
在这里插入图片描述
addSingletonFactory(beanName,singletonFactory) 添加一个单例工厂,这个工厂可以产生我们需要的半成品的 Bean 。

addSingletonFactory
在这里插入图片描述
下面这几行代码都很重要,依次分析下这几行代码:

先判断单例池 singletonObjects 中是否已经存在这个 Bean ,如果存在说明循环依赖已经完成。这里,我们称 singletonObjects一级缓存
在这里插入图片描述
如果一级缓存中不存在,将工厂对象 put 到 singletonFactories 中去,我们称之为二级缓存
在这里插入图片描述
earlySingletonObjects 中移除这个 Bean ,我们称 earlySingletonObjects三级缓存
在这里插入图片描述
先记住这三个缓存,我们先往下看,后面再来分析他们。

populateBean 这个方法完成了属性注入:
在这里插入图片描述
我们观察一下这行代码执行前后:
执行前:
只实例化了 x,y 还没实例化
在这里插入图片描述
执行后:
x 注入 y ,y 也注入了 x ,循环依赖完成!
在这里插入图片描述
然后绕来绕去又回到了 getSingleton:
在这里插入图片描述
但是这个 getSingleton 方法是我们上面看到的那个的兄弟(重载):
在这里插入图片描述
下面分析下这个兄弟:
在这里插入图片描述

  • 按照惯例还是先从单例池中获取(一级缓存)x,前面说过获取不到
  • 然后判断 x 是否在正在创建Bean 的集合当中,前面分析过这个集合现在存在 x y
  • if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) 成立进入分支
  • singletonObject = this.earlySingletonObjects.get(beanName); 从三级缓存中获取 x,根据前面的分析三级缓存当中现在什么都没有,故而返回nll
  • if (singletonFactory != null) 成立,进入分支
  • ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); 从二级缓存中获取一个 ObjectFactory 工厂对象,二级缓存中存在创建 x 的工厂,故而可以获取到
  • singletonObject = singletonFactory.getObject(); 拿到一个半成品的 x Bean
  • this.earlySingletonObjects.put(beanName, singletonObject); 把 x 放到三级缓存,
  • this.singletonFactories.remove(beanName); 把二级缓存中 x 清除
  • 二级缓存中只存在一个 y 了,而三级缓存中多了一个 x
    在这里插入图片描述
    至此,循环依赖完成。

总结

最后使用一张图来总结一下
在这里插入图片描述

参考资料

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值