在关于Spring的采访中,我们经常被问到一个问题,Spring是如何解决循环依赖问题的。这道题算得上是Spring面试的高频题了,因为如果不刻意去研究,相信即使看了源码,面试官也未必能一下子揣摩出其中的玄机。本文主要针对这个问题,从源码的角度讲解其实现原理。一、流程演示关于Springbean的创建,本质上就是对象的创建。既然是对象,读者一定要明白,一个完整的对象由两部分组成:当前对象的实例化和对象属性的实例化。在Spring中,对象的实例化是通过反射来实现的,对象的属性是在对象实例化后以一定的方式设置的。这个过程可以这样理解:理解了这一点,对循环依赖的理解就已经帮助了一大步。这里我们以两个类A和B为例进行说明,下面是A和B的声明:@ComponentpublicclassA{privateBb;publicvoidsetB(Bb){this.b=b;}}@ComponentpublicclassB{privateAa;publicvoidsetA(Aa){this.a=a;}}可以看出,这里A和B分别取对方为自己的全局属性。这里首先要说明的是,Spring是通过ApplicationContext.getBean()方法来实例化bean的。如果要获取的对象依赖于另一个对象,会先创建当前对象,然后递归调用ApplicationContext.getBean()方法获取依赖对象,最后将获取到的对象注入到当前对象中。这里我们以上面先初始化A对象实例为例。首先,Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例。由于Spring容器中没有A对象实例,它会创建一个A对象,然后发现它依赖于B对象,所以它会尝试递归传递ApplicationContext。getBean()方法获取了B对象的实例,但是此时Spring容器中还没有B对象的实例,所以还是先创建了B对象的实例。读者需要注意这个时间点。此时A对象和B对象已经创建并存储在Spring容器中,但是A对象的属性b和B对象的属性a还没有设置。在之前的Spring中创建了B对象后,Spring发现B对象依赖了属性A,所以此时它还是会尝试递归调用ApplicationContext.getBean()方法获取A对象的实例,因为Spring中已经存在A对象的实例,虽然它只是一个半成品(其属性b还没有初始化),但它也是目标bean,所以会返回一个A对象的实例。此时设置了B对象的属性a,然后ApplicationContext.getBean()方法递归返回,即返回B对象的实例,并将该实例设置为A的属性b目的。这时注意A对象的属性b和B对象的属性a已经设置了目标对象的实例。读者可能会疑惑,之前给对象B设置属性a时,这个A类属性还是个半成品。但是需要注意的是,这个A是一个引用,本质上就是一开始实例化的A对象。在上述递归过程的最后,Spring将得到的B对象实例设置为A对象的属性b。这里的A对象其实和实例B中设置的半成品A对象是同一个对象,它的引用地址是一样的,这里的值是为A对象的b属性设置的,其实值为为半成品的a属性设置。下面我们通过一个流程图来解释这个过程:图中的getBean()表示调用Spring的ApplicationContext.getBean()方法,这个方法中的参数代表了我们要获取的目标对象。Spring的核心思想,建议大家看看。图中黑色箭头表示一开始方法调用的方向。最后,Spring中缓存的A对象返回后,表示递归调用返回,用绿色箭头表示。从图中我们可以清楚的看出,B对象的a属性就是第三步注入的半成品A对象,A对象的b属性就是第二步注入的成品B对象。这时,半成品A对象就变成了成品A对象,因为它的属性已经设置好了。一张图看懂Springbeans的完整生命周期,推荐阅读。二、源码解释关于Spring处理循环依赖问题的方式,我们可以通过上面的流程图很容易理解。需要注意的一点是Spring如何将一开始生成的A对象标记为半成品,以及如何保存A对象。这里的标记工作是通过ApplicationContext的属性SetsingletonsCurrentlyInCreation来保存的,半成品A对象是通过Map>singletonFactories来保存的,其中ObjectFactory是一个工厂对象,调用它就可以调用getObject()方法获取目标对象。AbstractBeanFactory.doGetBean()方法中获取对象的方法如下:{//尝试通过bean名称获取目标bean对象,比如这里的A对象ObjectsharedInstance=getSingleton(beanName);//我们这里的目标对象都是单例if(mbd.isSingleton()){//这里我们尝试创建目标Object,第二个参数是ObjectFactory类型的对象,使用Java8的lamada//表达式写的,只要上面getSingleton()方法的返回值为空,这里的getSingleton()方法将被调用以创建//目标对象sharedInstance=getSingleton(beanName,()->{try{//尝试创建目标对象returncreateBean(beanName,mbd,args);}catch(BeansExceptionex){throwex;}});}return(T)bean;}这里的doGetBean()方法是一个非常关键的方法(其他中间省略代码),上面主要有两步。第一步getSingleton()方法的作用是尝试获取目标对象,如果没有,则尝试获取半成品目标对象;如果第一步没有获取到目标对象实例,则进入第二步,第二步中getSingleton()方法的作用是尝试创建目标对象,并注入其依赖的属性。关注微信公众号:Java技术栈,后台回复:spring,可以获取我整理的最新Spring教程,都是干货。这其实就是这里的主要逻辑。正如我们在上图中所指出的,doGetBean()方法在整个过程中会被调用3次。第一次调用时,会尝试获取A对象实例。这个时候用到第一个getSingleton()方法,由于已经创建的A对象没有成品或者半成品,所以这里获取的是null,然后调用第二个getSingleton()方法创建A对象的实例,然后doGetBean()方法被递归调用,试图将B对象的实例注入到A对象中。这时,由于Spring容器中还没有B对象的成品或半成品,所以还是会去到第二个getSingleton()方法,在这个方法中创建B对象的一个??实例。创建完成后,尝试获取其依赖的A。instance作为它的属性,所以它仍然会递归调用doGetBean()方法。这时候需要注意的是,因为已经有了A对象的半成品实例,所以这时候在尝试获取A对象的实例时,会用到第一个getSingleton()方法。获取半成品A对象的实例。然后返回实例,注入到B对象的属性a中,此时B对象实例化完成。然后递归返回实例化后的B对象,再将实例注入到A对象中,从而得到一个成品A对象。这里我们可以读到上面第一个getSingleton()方法:@NullableprotectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){//尝试从缓存中获取成品的目标对象,如果存在则直接返回ObjectsingletonObject=this.singletonObjects.get(beanName);//如果缓存中不存在目标对象,则判断当前对象是否已经在创建过程中。在前面的解释中,在第一次尝试获取A对象的实例后//,会将A对象标记为正在创建,所以当你最终尝试获取A对象时,这里的if判断会为真如果(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){synchronized(this.singletonObjects){singletonObject=this.earlySingletonObjects.get(beanName);if(singletonObject==null&&allowEarlyReference){//这里的singletonFactories是一个Map,它的key是bean的名字,value是一个ObjectFactory类型的对象,这里对于A和B,getObject()方法调用图返回A和B对象的实例,是否是半成品ObjectFactory>singletonFactory=this.singletonFactories.get(beanName);if(singletonFactory!=null){//获取singletonObject的目标对象实例=singletonFactory.getObject();this.earlySingletonObjects.put(beanName,singletonObject);这.singletonFactories.remove(beanName);}}}}returnsingletonObject;}这里会遇到一个问题,如何实例化A的半成品实例,然后如何将其封装为ObjectFactory类型的对象,放到上面的singletonFactories属性中这主要是在前面的第二个getSingleton()方法中,最终会通过传入的第二个参数调用createBean()方法,而这个方法的最终调用委托给另一个doCreateBean()方法进行,有以下内容一段代码:protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,final@NullableObject[]args)throwsBeanCreationException{//实例化当前试图获取的bean对象,比如A对象和B对象BeanWrapper就是在这里实例化的instanceWrapper=空;if(mbd.isSingleton()){instanceWrapper=this.factoryBeanInstanceCache.remove(beanName);}if(instanceWrapper==null){instanceWrapper=createBeanInstance(beanName,mbd,args);}//判断Spring是否配置支持目标bean提前暴露,即是否支持半成品bean提前暴露booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&&isSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure){//如果支持,当前生成的半成品bean会被放入singletonFactories中。这个singletonFactories//就是第一个getSingleton()方法中用到的singletonFactories属性,也就是说这里是bean的半成品封装地方。而这里的getEarlyBeanReference()本质上就是第三个参数,直接会放上去,也就是//目标bean直接返回addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));}try{//实例初始化完成后,这里是判断当前bean是否依赖其他bean。如果是,//会递归调用getBean()方法尝试获取目标beanpopulateBean(beanName,mbd,instanceWrapper);}catch(Throwableex){//Omit...}returnexposedObject;}此时,Spring整个解决循环依赖问题的实现思路比较清晰。整体流程,读者只需要了解两点:Spring递归获取目标bean及其依赖的bean;Spring在实例化一个bean时,分两步进行,首先实例化目标bean,然后Injectproperties。结合这两点,也就是说,Spring在实例化一个bean的时候,首先递归实例化它所依赖的所有bean,直到一个bean不依赖其他bean,然后返回这个实例,然后反递归设置获取的bean作为每个上层bean的属性。最后,如果您觉得这篇文章对您有点帮助,请点个赞。或者可以加入我的开发交流群:1025263163互相学习,我们会有专业的技术解答。如果您觉得这篇文章对您有用,请给我们的开源项目一个小星星:https://gitee。com/ZhongBangKe...非常感谢!JAVA学习手册:https://doc.crmeb.com技术交流论坛:https://q.crmeb.com