Spring中循环依赖的解决方法详析
前言
说起spring中循环依赖的解决办法,相信很多园友们都或多或少的知道一些,但当真的要详细说明的时候,可能又没法一下将它讲清楚。本文就试着尽自己所能,对此做出一个较详细的解读。另,需注意一点,下文中会出现类的实例化跟类的初始化两个短语,为怕园友迷惑,事先声明一下,本文的实例化是指刚执行完构造器将一个对象new出来,但还未填充属性值的状态,而初始化是指完成了属性的依赖注入。
一、先说说spring解决的循环依赖是什么
java中的循环依赖分两种,一种是构造器的循环依赖,另一种是属性的循环依赖。
构造器的循环依赖就是在构造器中有属性循环依赖,如下所示的两个类就属于构造器循环依赖:
@service public class student { @autowired private teacher teacher; public student (teacher teacher) { system.out.println("student init1:" + teacher); } public void learn () { system.out.println("student learn"); } }
@service public class teacher { @autowired private student student; public teacher (student student) { system.out.println("teacher init1:" + student); } public void teach () { system.out.println("teach:"); student.learn(); } }
这种循环依赖没有什么解决办法,因为jvm虚拟机在对类进行实例化的时候,需先实例化构造器的参数,而由于循环引用这个参数无法提前实例化,故只能抛出错误。
spring解决的循环依赖就是指属性的循环依赖,如下所示:
@service public class teacher { @autowired private student student; public teacher () { system.out.println("teacher init1:" + student); } public void teach () { system.out.println("teach:"); student.learn(); } }
@service public class student { @autowired private teacher teacher; public student () { system.out.println("student init:" + teacher); } public void learn () { system.out.println("student learn"); } }
测试扫描类:
@componentscan(value = "mypackage") public class scanconfig { }
测试启动类:
public class springtest { public static void main(string[] args) { annotationconfigapplicationcontext applicationcontext = new annotationconfigapplicationcontext(scanconfig.class); applicationcontext.getbean(teacher.class).teach(); } }
测试类执行结果:
student init:null teacher init:null teach: student learn
可以看到,在构造器执行的时候未完成属性的注入,而在调用方法的时候已经完成了注入。下面就一起看看spring内部是在何时完成的属性注入,又是如何解决的循环依赖。
二、循环依赖与属性注入
1、对于非懒加载的类,是在refresh方法中的 finishbeanfactoryinitialization(beanfactory) 方法完成的包扫描以及bean的初始化,下面就一起追踪下去。
protected void finishbeanfactoryinitialization(configurablelistablebeanfactory beanfactory) { // 其他代码 // instantiate all remaining (non-lazy-init) singletons. beanfactory.preinstantiatesingletons(); }
可以看到调用了beanfactory的一个方法,此处的beanfactory就是指我们最常见的那个defaultlistablebeanfactory,下面看它里面的这个方法。
2、defaultlistablebeanfactory的preinstantiatesingletons方法
public void preinstantiatesingletons() throws beansexception { list<string> beannames = new arraylist<>(this.beandefinitionnames); // trigger initialization of all non-lazy singleton beans... for (string beanname : beannames) { rootbeandefinition bd = getmergedlocalbeandefinition(beanname); if (!bd.isabstract() && bd.issingleton() && !bd.islazyinit()) { // 判断为非抽象类、是单例、非懒加载 才给初始化 if (isfactorybean(beanname)) { // 无关代码(针对factorybean的处理) } else { // 重要!!!普通bean就是在这里初始化的 getbean(beanname); } } } // 其他无关代码 }
可以看到,就是在此方法中循环spring容器中所有的bean,依次对其进行初始化,初始化的入口就是getbean方法
3、abstractbeanfactory的getbean跟dogetbean方法
追踪getbean方法:
public object getbean(string name) throws beansexception { return dogetbean(name, null, null, false); }
可见引用了重载的dogetbean方法,继续追踪之:
protected <t> t dogetbean(final string name, @nullable final class<t> requiredtype, @nullable final object[] args, boolean typecheckonly) throws beansexception { final string beanname = transformedbeanname(name); object bean; // 方法1)从三个map中获取单例类 object sharedinstance = getsingleton(beanname); // 省略无关代码 } else { // 如果是多例的循环引用,则直接报错 if (isprototypecurrentlyincreation(beanname)) { throw new beancurrentlyincreationexception(beanname); } // 省略若干无关代码 try { // create bean instance. if (mbd.issingleton()) { // 方法2) 获取单例对象 sharedinstance = getsingleton(beanname, () -> { try { //方法3) 创建objectfactory中getobject方法的返回值 return createbean(beanname, mbd, args); } catch (beansexception ex) { // explicitly remove instance from singleton cache: it might have been put there // eagerly by the creation process, to allow for circular reference resolution. // also remove any beans that received a temporary reference to the bean. destroysingleton(beanname); throw ex; } }); bean = getobjectforbeaninstance(sharedinstance, name, beanname, mbd); } } // 省略若干无关代码 return (t) bean; }
该方法比较长,对于解决循环引用来说,上面标出来的3个方法起到了至关重要的作用,下面我们挨个攻克。
3.1) getsingleton(beanname)方法: 注意该方法跟方法2)是重载方法,名字一样内部逻辑却大相径庭。
protected object getsingleton(string beanname, boolean allowearlyreference) { object singletonobject = this.singletonobjects.get(beanname);// 步骤a if (singletonobject == null && issingletoncurrentlyincreation(beanname)) { synchronized (this.singletonobjects) { singletonobject = this.earlysingletonobjects.get(beanname);// 步骤b if (singletonobject == null && allowearlyreference) { objectfactory<?> singletonfactory = this.singletonfactories.get(beanname);// 步骤c if (singletonfactory != null) { singletonobject = singletonfactory.getobject(); this.earlysingletonobjects.put(beanname, singletonobject); this.singletonfactories.remove(beanname); } } } } return singletonobject; }
通过上面的步骤可以看出这三个map的优先级。其中singletonobjects里面存放的是初始化之后的单例对象;earlysingletonobjects中存放的是一个已完成实例化未完成初始化的早期单例对象;而singletonfactories中存放的是objectfactory对象,此对象的getobject方法返回值即刚完成实例化还未开始初始化的单例对象。所以先后顺序是,单例对象先存在于singletonfactories中,后存在于earlysingletonobjects中,最后初始化完成后放入singletonobjects中。
当debug到此处时,以上述teacher和student两个循环引用的类为例,如果第一个走到这一步的是teacher,则从此处这三个map中get到的值都是空,因为还未添加进去。这个方法主要是给循环依赖中后来过来的对象用。
3.2)getsingleton(string beanname, objectfactory<?> singletonfactory)方法:
public object getsingleton(string beanname, objectfactory<?> singletonfactory) { assert.notnull(beanname, "bean name must not be null"); synchronized (this.singletonobjects) { object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null) { // 省略无关代码 beforesingletoncreation(beanname); // 步骤a boolean newsingleton = false; // 省略无关代码 try { singletonobject = singletonfactory.getobject();// 步骤b newsingleton = true; } // 省略无关代码 finally { if (recordsuppressedexceptions) { this.suppressedexceptions = null; } aftersingletoncreation(beanname);// 步骤c } if (newsingleton) { addsingleton(beanname, singletonobject);// 步骤d } } return singletonobject; } }
获取单例对象的主要逻辑就是此方法实现的,主要分为上面四个步骤,继续挨个看:
步骤a:
protected void beforesingletoncreation(string beanname) { // 判断,并首次将beanname即teacher放入singletonscurrentlyincreation中 if (!this.increationcheckexclusions.contains(beanname) && !this.singletonscurrentlyincreation.add(beanname)) { throw new beancurrentlyincreationexception(beanname); } }
步骤c:
protected void aftersingletoncreation(string beanname) { // 得到单例对象后,再讲beanname从singletonscurrentlyincreation中移除 if (!this.increationcheckexclusions.contains(beanname) && !this.singletonscurrentlyincreation.remove(beanname)) { throw new illegalstateexception("singleton '" + beanname + "' isn't currently in creation"); } }
步骤d:
protected void addsingleton(string beanname, object singletonobject) { synchronized (this.singletonobjects) { this.singletonobjects.put(beanname, singletonobject);//添加单例对象到map中 this.singletonfactories.remove(beanname);//从早期暴露的工厂中移除,此map在解决循环依赖中发挥了关键的作用 this.earlysingletonobjects.remove(beanname);//从早期暴露的对象map中移除 this.registeredsingletons.add(beanname);//添加到已注册的单例名字集合中 } }
步骤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[]) 方法
protected object createbean(string beanname, rootbeandefinition mbd, @nullable object[] args) throws beancreationexception { // 省略无关代码 try { object beaninstance = docreatebean(beanname, mbdtouse, args); return beaninstance; } // 省略无关代码 }
去掉无关代码之后,关键方法只有docreatebean方法,追踪之:
protected object docreatebean(final string beanname, final rootbeandefinition mbd, final @nullable object[] args) throws beancreationexception { beanwrapper instancewrapper = null; // 省略代码 if (instancewrapper == null) { // 实例化bean instancewrapper = createbeaninstance(beanname, mbd, args); } boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences && issingletoncurrentlyincreation(beanname)); if (earlysingletonexposure) { // 重点!!!将实例化的对象添加到singletonfactories中 addsingletonfactory(beanname, () -> getearlybeanreference(beanname, mbd, bean)); } // 初始化bean object exposedobject = bean; try { populatebean(beanname, mbd, instancewrapper);//也很重要 exposedobject = initializebean(beanname, exposedobject, mbd); } // 省略无关代码 return exposedobject; }
上面注释中标出的重点是此方法的关键。在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,解决了循环依赖的问题。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。