本文简要介绍了循环依赖以及Spring解决循环依赖的过程
循环依赖是指对象之间的循环依赖,即2个或以上的对象互相持有对方,最终形成闭环。这里的对象特指单例对象。
对象之间的循环依赖主要有两种表现形式:构造函数循环依赖和属性循环依赖。
2-1 构造函数循环依赖
A, B,之间形成的循环依赖,具体表现在:A构造函数的参数是B,B构造函数的参数是A,即构造函数循环依赖。
将这2个bean交给Spring容器管理,并使用构造函数注入来模拟构造函数循环依赖。
测试构造函数循环依赖:
测试结果:
这种构造函数循环依赖无法解决,因为JVM在对类进行实例化时,需要先实例化构造函数的参数,而由于各个构造函数的参数之间是循环依赖的,所以各个参数都无法提前实例化,故只能抛出错误。
2-2 属性循环依赖
A, B,之间形成的循环依赖,具体表现在:A一个属性是B,B一个属性是A,即属性循环依赖。
将这2个bean交给Spring容器管理,并使用属性注入来模拟属性循环依赖。
测试属性循环依赖:
测试结果:
这种表现形式的循环依赖Spring可以解决,大致的思路是:Spring先使用各个类的无参构造函数实例化各个对象,再使用Setter去设置对象的属性。但Spring在实例化各个对象时并不是按定义顺序去实例化的,中间还穿插了递归实例化的过程。
假定有A ,B, C三个类,A, C之间存在着循环依赖关系(即A有个C类型的属性,C有个A类型的属性),XML配置文件中定义的顺序为A->B->C。
Spring首先会去实例化A,A实例化完成后,为A注入其属性C的值,在注入属性C的时候发现C还未被实例化就先去实例化C,实例化C完成后,为C注入属性A,而此时A已经完成实例化了,所以可以直接为C注入属性A,然后完成对A中的C属性注入,最后再去实例化B。所以实例化顺序是A-> C->B而不是A->B->C。
在某个对象创建的过程中,如果递归调用回来发现自己正在创建过程中的话,即说明存在循环依赖。
Spring解决循环依赖的主要依据是Java的引用传递:当我们获取到对象的引用时,对象的属性可以是还未设置的(允许延后设置属性),但构造器的执行必须在获取引用之前。
Spring单例对象的初始化主要分为3步:
第1步 实例化:调用对象的构造函数实例化对象;
第2步 注入属性:对实例化对象的属性值进行注入;
第3步 初始化:调用SpringXML中的init()方法。
由上述初始化步骤可知,循环依赖主要发生在第1,2 步,即:构造器循环依赖和属性循环依赖。Spring解决属性循环依赖使用了三级缓存。
Spring版本信息为:
5-1 三级缓存
三级缓存的源码(调换了顺序):
三级缓存:
一级缓存->singletonObjects:存放的是初始化之后的单例对象
二级缓存->earlySingletonObjects:存放的是一个已完成实例化未完成初始化的早期单例对象
三级缓存->singletonFactories:存放的是可产生单例对象的工厂对象(此缓存中的bean name和二级缓存中的bean name是互斥的,即此级缓存中有某个bean的工厂,二级缓存中就没有这个bean)
5-2 具体实现
对于非懒加载的类,是在refresh方法中的 finishBeanFactoryInitialization(beanFactory) 方法完成的包扫描以及bean的初始化。
此处调用了beanFactory的一个方法preInstantiateSingletons(),此处的beanFactory是DefaultListableBeanFactory。
DefaultListableBeacFactory的preInstantiateSingletons()方法:
在此方法中,循环Spring容器中的所有Bean,依次对其进行初始化,初始化的入口就是getBean()方法。
追踪getBean方法:
引用了AbstractBeanFacotry中重载的doGetBean()方法:
追踪doGetBean()方法:
该方法中使用的方法① 、方法②、方法③对解决循环依赖起了至关重要的作用,下面来依次分析:
方法①:getSingleton(String beanName, boolean allowEarlyReference)
通过上面的步骤可以看出这三级缓存的优先级。其中singletonObjects里面存放的是初始化之后的单例对象;earlySingletonObjects中存放的是一个已完成实例化未完成初始化的早期单例对象;而singletonFactories中存放的是ObjectFactory对象,此对象的getObject方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中。
当debug到此处时,以2-2中的A, B为例,第一步走到这个方法的是A,此时从这三个缓存中获取到的都是null,因为还没有内容被添加到这三级缓存中去。这个方法主要是给循环依赖中后过来的对象使用。
方法②:getSingleton(String beanName, ObjectFactory singletonFactory)
获取单例对象的主要逻辑就是次方法实现的,主要分为上面4个步骤:
步骤A:
首次将beanName放入到singletonCurrentlyInCreation中
步骤C:
得到单例对象后,再讲beanName从singletonsCurrentlyInCreation中移除
步骤D:
步骤B:
此处调用了ObjectFactory的getObject方法,此方法是在方法③中实现的
步骤B其实也就是 方法③
去掉无关代码后,关键方法只有doCreateBean() 方法
doCreateBean() 方法:
上面代码中[★重要]标注的重点是此方法的关键。在addSingletonFacotry()方法中,将< beanName->能创建该bean的工厂>键值对添加到了三级缓存中以供其他对象依赖时调用。
populateBean()方法对刚实例化的bean进行属性注入,如果遇到要注入的属性对应的值是由Spring管理的bean,则再通过getBean方法获取该bean。
5-3 总结
属性注入主要是在populateBean() 方法中进行的。对于循环依赖,以2-2中的A, B为例,假定Spring的加载顺序为先加载A。其具体的加载过程为:
参考源:
1. https://www.cnblogs.com/leeego-123/p/12165278.html
2. https://blog.csdn.net/qq_36381855/article/details/79752689Original: https://www.cnblogs.com/certainTao/p/14405590.html
Author: certainTao
Title: Spring的循环依赖
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/711762/
转载文章受原作者版权保护。转载请注明原作者出处!