首页 文章资讯内容详情

Spring中的循环依赖解决详解

2026-06-01 2 花语

本文内容纲要:

前言

说起Spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚。本文就试着尽自己所能,对此做出一个较详细的解读。另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而初始化是指完成了属性的依赖注入。

一、先说说Spring解决的循环依赖是什么

Java中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖。

构造器的循环依赖就是在构造器中有属性循环依赖,如下所示的两个类就属于构造器循环依赖:

1@Service 2publicclassStudent{ 3@Autowired 4privateTeacherteacher; 5 6publicStudent(Teacherteacher){ 7System.out.println("Studentinit1:"+teacher); 8} 9 10publicvoidlearn(){ 11System.out.println("Studentlearn"); 12} 13} 1@Service 2publicclassTeacher{ 3@Autowired 4privateStudentstudent; 5 6publicTeacher(Studentstudent){ 7System.out.println("Teacherinit1:"+student); 8 9} 10 11publicvoidteach(){ 12System.out.println("teach:"); 13student.learn(); 14} 15}

这种循环依赖没有什么解决办法,因为JVM虚拟机在对类进行实例化的时候,需先实例化构造器的参数,而由于循环引用这个参数无法提前实例化,故只能抛出错误。

Spring解决的循环依赖就是指属性的循环依赖,如下所示:

1@Service 2publicclassTeacher{ 3@Autowired 4privateStudentstudent; 5 6publicTeacher(){ 7System.out.println("Teacherinit1:"+student); 8 9} 10 11publicvoidteach(){ 12System.out.println("teach:"); 13student.learn(); 14} 15 16} 1@Service 2publicclassStudent{ 3@Autowired 4privateTeacherteacher; 5 6publicStudent(){ 7System.out.println("Studentinit:"+teacher); 8} 9 10publicvoidlearn(){ 11System.out.println("Studentlearn"); 12} 13}

测试扫描类:

1@ComponentScan(value="myPackage") 2publicclassScanConfig{ 3 4}

测试启动类:

1publicclassSpringTest{ 2 3publicstaticvoidmain(String[]args){ 4AnnotationConfigApplicationContextapplicationContext=newAnnotationConfigApplicationContext(ScanConfig.class); 5applicationContext.getBean(Teacher.class).teach(); 6 7} 8}

测试类执行结果:

1Studentinit:null 2Teacherinit:null 3teach: 4Studentlearn

可以看到,在构造器执行的时候未完成属性的注入,而在调用方法的时候已经完成了注入。下面就一起看看Spring内部是在何时完成的属性注入,又是如何解决的循环依赖。

二、循环依赖与属性注入

1、对于非懒加载的类,是在refresh方法中的finishBeanFactoryInitialization(beanFactory)方法完成的包扫描以及bean的初始化,下面就一起追踪下去。

1protectedvoidfinishBeanFactoryInitialization(ConfigurableListableBeanFactorybeanFactory){ 2//其他代码 3 4//Instantiateallremaining(non-lazy-init)singletons. 5beanFactory.preInstantiateSingletons(); 6}

可以看到调用了beanFactory的一个方法,此处的beanFactory就是指我们最常见的那个DefaultListableBeanFactory,下面看它里面的这个方法。

2、DefaultListableBeanFactory的preInstantiateSingletons方法

1publicvoidpreInstantiateSingletons()throwsBeansException{ 2 3List<String>beanNames=newArrayList<>(this.beanDefinitionNames); 4 5//Triggerinitializationofallnon-lazysingletonbeans... 6for(StringbeanName:beanNames){ 7RootBeanDefinitionbd=getMergedLocalBeanDefinition(beanName); 8if(!bd.isAbstract()&&bd.isSingleton()&&!bd.isLazyInit()){//判断为非抽象类、是单例、非懒加载才给初始化 9if(isFactoryBean(beanName)){ 10//无关代码(针对FactoryBean的处理) 11} 12else{ 13//重要!!!普通bean就是在这里初始化的 14getBean(beanName); 15} 16} 17} 18 19//其他无关代码 20}

可以看到,就是在此方法中循环Spring容器中所有的bean,依次对其进行初始化,初始化的入口就是getBean方法

3、AbstractBeanFactory的getBean跟doGetBean方法

追踪getBean方法:

1publicObjectgetBean(Stringname)throwsBeansException{ 2returndoGetBean(name,null,null,false); 3}

可见引用了重载的doGetBean方法,继续追踪之:

1protected<T>TdoGetBean(finalStringname,@NullablefinalClass<T>requiredType, 2@NullablefinalObject[]args,booleantypeCheckOnly)throwsBeansException{ 3 4finalStringbeanName=transformedBeanName(name); 5Objectbean; 6 7//方法1)从三个map中获取单例类 8ObjectsharedInstance=getSingleton(beanName); 9//省略无关代码 10} 11else{ 12//如果是多例的循环引用,则直接报错 13if(isPrototypeCurrentlyInCreation(beanName)){ 14thrownewBeanCurrentlyInCreationException(beanName); 15} 16//省略若干无关代码 17try{ 18//Createbeaninstance. 19if(mbd.isSingleton()){ 20//方法2)获取单例对象 21sharedInstance=getSingleton(beanName,()->{ 22try{//方法3)创建ObjectFactory中getObject方法的返回值 23returncreateBean(beanName,mbd,args); 24} 25catch(BeansExceptionex){ 26//Explicitlyremoveinstancefromsingletoncache:Itmighthavebeenputthere 27//eagerlybythecreationprocess,toallowforcircularreferenceresolution. 28//Alsoremoveanybeansthatreceivedatemporaryreferencetothebean. 29destroySingleton(beanName); 30throwex; 31} 32}); 33bean=getObjectForBeanInstance(sharedInstance,name,beanName,mbd); 34} 35} 36//省略若干无关代码 37return(T)bean; 38}

该方法比较长,对于解决循环引用来说,上面标出来的3个方法起到了至关重要的作用,下面我们挨个攻克。

3.1)getSingleton(beanName)方法注意该方法跟方法2)是重载方法,名字一样内部逻辑却大相径庭。

1protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){ 2ObjectsingletonObject=this.singletonObjects.get(beanName);//步骤A 3if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){ 4synchronized(this.singletonObjects){ 5singletonObject=this.earlySingletonObjects.get(beanName);//步骤B 6if(singletonObject==null&&allowEarlyReference){ 7ObjectFactory<?>singletonFactory=this.singletonFactories.get(beanName);//步骤C 8if(singletonFactory!=null){ 9singletonObject=singletonFactory.getObject(); 10this.earlySingletonObjects.put(beanName,singletonObject); 11this.singletonFactories.remove(beanName); 12} 13} 14} 15} 16returnsingletonObject; 17}

通过上面的步骤可以看出这三个map的优先级。其中singletonObjects里面存放的是初始化之后的单例对象;earlySingletonObjects中存放的是一个已完成实例化未完成初始化的早期单例对象;而singletonFactories中存放的是ObjectFactory对象,此对象的getObject方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于singletonFactories中,后存在于earlySingletonObjects中,最后初始化完成后放入singletonObjects中

当debug到此处时,以上述Teacher和Student两个循环引用的类为例,如果第一个走到这一步的是Teacher,则从此处这三个map中get到的值都是空,因为还未添加进去。这个方法主要是给循环依赖中后来过来的对象用。

3.2)getSingleton(StringbeanName,ObjectFactory<?>singletonFactory)方法

1publicObjectgetSingleton(StringbeanName,ObjectFactory<?>singletonFactory){ 2Assert.notNull(beanName,"Beannamemustnotbenull"); 3synchronized(this.singletonObjects){ 4ObjectsingletonObject=this.singletonObjects.get(beanName); 5if(singletonObject==null){ 6//省略无关代码 7beforeSingletonCreation(beanName);//步骤A 8booleannewSingleton=false; 9//省略无关代码 10try{ 11singletonObject=singletonFactory.getObject();//步骤B 12newSingleton=true; 13} 14//省略无关代码 15finally{ 16if(recordSuppressedExceptions){ 17this.suppressedExceptions=null; 18} 19afterSingletonCreation(beanName);//步骤C 20} 21if(newSingleton){ 22addSingleton(beanName,singletonObject);//步骤D 23} 24} 25returnsingletonObject; 26} 27}

获取单例对象的主要逻辑就是此方法实现的,主要分为上面四个步骤,继续挨个看:

步骤A:

1protectedvoidbeforeSingletonCreation(StringbeanName){ 2//判断,并首次将beanName即teacher放入singletonsCurrentlyInCreation中 3if(!this.inCreationCheckExclusions.contains(beanName)&&!this.singletonsCurrentlyInCreation.add(beanName)){ 4thrownewBeanCurrentlyInCreationException(beanName); 5} 6}

步骤C:

1protectedvoidafterSingletonCreation(StringbeanName){ 2//得到单例对象后,再讲beanName从singletonsCurrentlyInCreation中移除 3if(!this.inCreationCheckExclusions.contains(beanName)&&!this.singletonsCurrentlyInCreation.remove(beanName)){ 4thrownewIllegalStateException("Singleton"+beanName+"isntcurrentlyincreation"); 5} 6}

步骤D:

1protectedvoidaddSingleton(StringbeanName,ObjectsingletonObject){ 2synchronized(this.singletonObjects){ 3this.singletonObjects.put(beanName,singletonObject);//添加单例对象到map中 4this.singletonFactories.remove(beanName);//从早期暴露的工厂中移除,此map在解决循环依赖中发挥了关键的作用 5this.earlySingletonObjects.remove(beanName);//从早期暴露的对象map中移除 6this.registeredSingletons.add(beanName);//添加到已注册的单例名字集合中 7} 8}

步骤B:

此处调用了ObjectFactory的getObject方法,此方法是在哪里实现的呢?返回的又是什么?且往回翻,找到3中的方法3,对java8函数式编程有过了解的园友应该能看出来,方法3【createBean(beanName,mbd,args)】的返回值就是getObject方法的返回值,即方法3返回的就是我们需要的单例对象,下面且追踪方法3而去。

3.3)AbstractAutowireCapableBeanFactory#createBean(java.lang.String,org.springframework.beans.factory.support.RootBeanDefinition,java.lang.Object[])方法

1protectedObjectcreateBean(StringbeanName,RootBeanDefinitionmbd,@NullableObject[]args) 2throwsBeanCreationException{ 3 4//省略无关代码 5try{ 6ObjectbeanInstance=doCreateBean(beanName,mbdToUse,args); 7returnbeanInstance; 8} 9//省略无关代码 10}

去掉无关代码之后,关键方法只有doCreateBean方法,追踪之:

1protectedObjectdoCreateBean(finalStringbeanName,finalRootBeanDefinitionmbd,final@NullableObject[]args) 2throwsBeanCreationException{ 3 4BeanWrapperinstanceWrapper=null; 5//省略代码 6if(instanceWrapper==null){ 7//实例化bean 8instanceWrapper=createBeanInstance(beanName,mbd,args); 9} 10booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&& 11isSingletonCurrentlyInCreation(beanName)); 12if(earlySingletonExposure){ 13//重点!!!将实例化的对象添加到singletonFactories中 14addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean)); 15} 16//初始化bean 17ObjectexposedObject=bean; 18try{ 19populateBean(beanName,mbd,instanceWrapper);//也很重要 20exposedObject=initializeBean(beanName,exposedObject,mbd); 21} 22//省略无关代码 23returnexposedObject; 24}

上面注释中标出的重点是此方法的关键。在addSingletonFactory方法中,将第二个参数ObjectFactory存入了singletonFactories供其他对象依赖时调用。然后下面的populateBean方法对刚实例化的bean进行属性注入(该方法关联较多,本文暂时不展开追踪了,有兴趣的园友自行查看即可),如果遇到Spring中的对象属性,则再通过getBean方法获取该对象。至此,循环依赖在Spring中的处理过程已经追溯完毕,下面我们总结一下。

小结

属性注入主要是在populateBean方法中进行的。对于循环依赖,以我们上文中的Teacher中注入了Student、Student中注入了Teacher为例来说明,假定Spring的加载顺序为先加载Teacher,再加载Student。

getBean方法触发Teacher的初始化后:

a.首先走到3中的方法1),此时map中都为空,获取不到实例;

b.然后走到方法2)中,步骤A、步骤C、步骤D为控制map中数据的方法,实现简单,可暂不关注。其中步骤B的getObject方法触发对方法3)的调用;

c.在方法3)中,先通过createBeanInstance实例化Teacher对象,又将该实例化的对象通过addSingletonFactory方法放入singletonFactories中,完成Teacher对象早期的暴露;

d.然后在方法3)中通过populateBean方法对Teacher对象进行属性的注入,发现它有一个Student属性,则触发getBean方法对Student进行初始化

e.重复a、b、c步骤,只是此时要初始化的是Student对象

f.走到d的时候,调用populateBean对Student对象进行属性注入,发现它有一个Teacher属性,则触发getBean方法对Teacher进行初始化;

g.对Teacher进行初始化,又来到a,但此时map已经不为空了,因为之前在c步骤中已经将Teacher实例放入了singletonFactories中,a中得到Teacher实例后返回;

h.完成f中对Student的初始化,继而依次往上回溯完成Teacher的初始化;

完成Teacher的初始化后,Student的初始化就简单了,因为map中已经存了这个单例。

至此,Spring循环依赖的总结分析结束,一句话来概括一下:Spring通过将实例化后的对象提前暴露给Spring容器中的singletonFactories,解决了循环依赖的问题

本文内容总结:

原文链接:https://www.cnblogs.com/zzq6032010/p/11406405.html