Spring循环依赖正确性及Bean注入的顺序关系详解
一、前言
我们知道 spring 可以是懒加载的,就是当真正使用到 bean 的时候才实例化 bean。当然也不全是这样,例如配置 bean 的 lazy-init 属性,可以控制 spring 的加载时机。现在机器的性能、内存等都比较高,基本上也不使用懒加载,在容器启动时候来加载bean,启动时间稍微长一点儿,这样在实际获取 bean 供业务使用时,就可以减轻不少负担,这个后面再做分析。 我们使用到 bean 的时候,最直接的方式就是从 factroy 中获取,这个就是加载 bean 实例的源头。
最近在做项目时候遇到一个奇葩问题,就是bean依赖注入的正确性与bean直接注入的顺序有关系,但是正常情况下明明是和顺序没关系的啊,究竟啥情况那,不急,让我一一道来。
二、普通bean循环依赖-与注入顺序无关
2.1 循环依赖例子与原理
public class beana { private beanb beanb; public beanb getbeanb() { return beanb; } public void setbeanb(beanb beanb) { this.beanb = beanb; } }
public class beanb { private beana beana; public beana getbeana() { return beana; } public void setbeana(beana beana) { this.beana = beana; } }
<bean id="beana" class="com.alibaba.test.circle.beana"> <property name="beanb"> <ref bean="beanb" /> </property> </bean>
<bean id="beanb" class="com.alibaba.test.circle.beanb"> <property name="beana"> <ref bean="beana" /> </property> </bean>
上述循环依赖注入能够正常工作,这是因为spring提供了earlybeanreference功能,首先spring里面有个名字为singletonobjects的并发map用来存放所有实例化并且初始化好的bean,singletonfactories则用来存放需要解决循环依赖的bean信息(beanname,和一个回调工厂)。当实例化beana时候会触发getbean(“beana”);
首先看singletonobjects中是否有beana有则返回:
(1)
object sharedinstance = getsingleton(beanname);//getsingleton(beanname,true); if (sharedinstance != null && args == null) { if (logger.isdebugenabled()) { if (issingletoncurrentlyincreation(beanname)) { logger.debug("returning eagerly cached instance of singleton bean '" + beanname + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("returning cached instance of singleton bean '" + beanname + "'"); } } // 如果是普通bean直接返回,工厂bean则返回sharedinstance.getobject(); bean = getobjectforbeaninstance(sharedinstance, name, beanname, null); }
protected object getsingleton(string beanname, boolean allowearlyreference) { object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null) { synchronized (this.singletonobjects) { singletonobject = this.earlysingletonobjects.get(beanname); if (singletonobject == null && allowearlyreference) { objectfactory singletonfactory = (objectfactory) this.singletonfactories.get(beanname); if (singletonfactory != null) { singletonobject = singletonfactory.getobject(); this.earlysingletonobjects.put(beanname, singletonobject); this.singletonfactories.remove(beanname); } } } } return (singletonobject != null_object ? singletonobject : null); }
一开始肯定没有所以会实例化beana,如果设置了allowcircularreferences=true
(默认为true)并且当前bean为单件并且该bean目前在创建中,则初始化属性前把该bean信息放入singletonfactories单件map里面:
(2)
boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences && issingletoncurrentlyincreation(beanname));
if (earlysingletonexposure) { if (logger.isdebugenabled()) { logger.debug("eagerly caching bean '" + beanname + "' to allow for resolving potential circular references"); } addsingletonfactory(beanname, new objectfactory() { public object getobject() throws beansexception { return getearlybeanreference(beanname, mbd, bean); } }); }
protected void addsingletonfactory(string beanname, objectfactory singletonfactory) { assert.notnull(singletonfactory, "singleton factory must not be null"); synchronized (this.singletonobjects) { if (!this.singletonobjects.containskey(beanname)) { this.singletonfactories.put(beanname, singletonfactory); this.earlysingletonobjects.remove(beanname); this.registeredsingletons.add(beanname); } } }
然后对该实例进行属性注入beanb,属性注入时候会getbean(“beanb”)
,发现beanb 不在singletonobjects中,就会实例化beanb,然后放入singletonfactories,然后进行属性注入beana,然后触发getbean(“beana”);
这时候会到(1)getsingleton返回实例化的beana。到此beanb初始化完毕添加beanb 到singletonobjects然后返回,然后beana 初始化完毕,添加beana到singletonobjects然后返回
2.2 允许循环依赖的开关
public class testcircle2 { private final static classpathxmlapplicationcontext modulecontext; private static test test; static { modulecontext = new classpathxmlapplicationcontext(new string[]{"beans-circile.xml"}); modulecontext.setallowcircularreferences(false); test = (test) modulecontext.getbean("test"); } public static void main(string[] args) { system.out.println(test.name); } }
classpathxmlapplicationcontext类中有个属性allowcircularreferences用来控制是否允许循环依赖默认为true,这里设置为false后发现循环依赖还是可以正常运行,翻看源码:
public classpathxmlapplicationcontext(string[] configlocations) throws beansexception { this(configlocations, true, null); }
public classpathxmlapplicationcontext(string[] configlocations, boolean refresh, applicationcontext parent) throws beansexception { super(parent); setconfiglocations(configlocations); if (refresh) { refresh(); } }
public classpathxmlapplicationcontext(string[] configlocations, boolean refresh, applicationcontext parent) throws beansexception { super(parent); setconfiglocations(configlocations); if (refresh) { refresh(); } }
知道默认构造classpathxmlapplicationcontext时候会刷新容器。
refresh方法会调用refreshbeanfactory:
protected final void refreshbeanfactory() throws beansexception { if (hasbeanfactory()) { destroybeans(); closebeanfactory(); } try { // 创建bean工厂 defaultlistablebeanfactory beanfactory = createbeanfactory(); //定制bean工厂属性 customizebeanfactory(beanfactory); loadbeandefinitions(beanfactory); synchronized (this.beanfactorymonitor) { this.beanfactory = beanfactory; } } catch (ioexception ex) { throw new applicationcontextexception( "i/o error parsing xml document for application context [" + getdisplayname() + "]", ex); } }
protected void customizebeanfactory(defaultlistablebeanfactory beanfactory) { if (this.allowbeandefinitionoverriding != null) { beanfactory.setallowbeandefinitionoverriding(this.allowbeandefinitionoverriding.booleanvalue()); } if (this.allowcircularreferences != null) { beanfactory.setallowcircularreferences(this.allowcircularreferences.booleanvalue()); } }
到这里就知道了,我们在调用 modulecontext.setallowcircularreferences(false)
前,spring留出的设置bean工厂的回调customizebeanfactory已经执行过了,最终原因是,调用设置前,bean工厂已经refresh了,所以测试代码改为:
public class testcircle { private final static classpathxmlapplicationcontext modulecontext; private static test test; static { //初始化容器上下文,但是不刷新容器 modulecontext = new classpathxmlapplicationcontext(new string[]{"beans-circile.xml"},false); modulecontext.setallowcircularreferences(false); //刷新容器 modulecontext.refresh(); test = (test) modulecontext.getbean("test"); } public static void main(string[] args) { system.out.println(test.name); } }
现在测试就会抛出异常:
caused by: org.springframework.beans.factory.beancreationexception: error creating bean with name 'beana' defined in class path resource [beans-circile.xml]: cannot resolve reference to bean 'beanb' while setting bean property 'beanb'; nested exception is org.springframework.beans.factory.beancreationexception: error creating bean with name 'beanb' defined in class path resource [beans-circile.xml]: cannot resolve reference to bean 'beana' while setting bean property 'beana'; nested exception is org.springframework.beans.factory.beancurrentlyincreationexception: error creating bean with name 'beana': requested bean is currently in creation: is there an unresolvable circular reference?
三、工厂bean与普通bean循环依赖-与注入顺序有关
3.1 测试代码
工厂bean
public class myfactorybean implements factorybean,initializingbean{ private string name; private test test; public string getname() { return name; } public void setname(string name) { this.name = name; } public dependentbean getdepentbean() { return depentbean; } public void setdepentbean(dependentbean depentbean) { this.depentbean = depentbean; } private dependentbean depentbean; public object getobject() throws exception { return test; } public class getobjecttype() { // todo auto-generated method stub return test.class; } public boolean issingleton() { // todo auto-generated method stub return true; } public void afterpropertiesset() throws exception { system.out.println("name:" + this.name); test = new test(); test.name = depentbean.dosomething() + this.name; } }
为了简化,只写一个public的变量
public class test { public string name; }
public class dependentbean { public string dosomething(){ return "hello:"; } @autowired private test test; }
xml配置
<bean id="test" class="com.alibaba.test.circle.myfactorybean"> <property name="depentbean"> <bean class="com.alibaba.test.circle.dependentbean"></bean> </property> <property name="name" value="zlx"></property> </bean>
其中工厂bean myfactorybean作用是对test类的包装,首先对myfactorybean设置属性,然后在myfactorybean的afterpropertiesset方法中创建一个test实例,并且设置属性,实例化myfactorybean最终会调用getobject方法返回创建的test对象。这里myfactorybean依赖了depentbean,而depentbean本身有依赖了test,所以这是个循环依赖
测试:
public class testcircle2 { private final static classpathxmlapplicationcontext modulecontext; private static test test; static { modulecontext = new classpathxmlapplicationcontext(new string[]{"beans-circile.xml"}); test = (test) modulecontext.getbean("test"); } public static void main(string[] args) { system.out.println(test.name); } }
结果:
caused by: org.springframework.beans.factory.beancreationexception: error creating bean with name 'com.alibaba.test.circle.dependentbean#1c701a27': autowiring of fields failed; nested exception is org.springframework.beans.factory.beancreationexception: could not autowire field: private com.alibaba.test.circle.test com.alibaba.test.circle.dependentbean.test; nested exception is org.springframework.beans.factory.beancurrentlyincreationexception: error creating bean with name 'test': factorybean which is currently in creation returned null from getobject
3.2 分析原因
当实例化test时候会触发getbean(“test”)
,会看当前bean是否存在
不存在则创建test 的实例,创建完毕后会把当前bean信息放入singletonfactories单件map里面
然后对该实例进行属性注入depentbean,属性注入时候会getbean(“depentbean”)
,
发现depentbean 不存在,就会实例化depentbean,然后放入singletonfactories,
然后进行autowired注入test,然后触发getbean(“test”);
这时候会到(1)getsingleton返回实例化的test。由于test是工厂bean所以返回test.getobject();
而myfactorybean的afterpropertiesset还没被调用,所以test.getobject()
返回null.
下面列下spring bean创建的流程:
getbean()->创建实例->autowired->set属性->afterpropertiesset
也就是调用getobject方法早于afterpropertiesset方法被调用了。
那么我们修改下myfactorybean为如下:
public object getobject() throws exception { // todo auto-generated method stub if(null == test){ afterpropertiesset(); } return test; }
public void afterpropertiesset() throws exception { if(null == test){ system.out.println("name:" + this.name); test = new test(); test.name = depentbean.dosomething() + this.name; } }
也就是getobject内部先判断不如test==null
那调用下afterpropertiesset,然后afterpropertiesset内部如果test==null
在创建test实例,看起来貌似不错,好想可以解决我们的问题。但是实际上还是不行的,因为afterpropertiesset内部使用了depentbean,而此时depentbean=null
。
3.3 思考如何解决
3.2分析原因是先创建了myfactorybean,并在在创建myfactorybean的过程中有创建了depentbean,而创建depentbean时候需要autowired myfactorybean的实例,然后要调用afterpropertiesset前调用getobject方法所以返回null。
那如果先创建depentbean,然后在创建myfactorybean那?下面分析下过程:
首先会实例化depentbean,并且加入到singletonfactories
depentbean实例会autowired test,所以会先创建test实例
创建test实例,然后加入singletonfactories
test实例会属性注入depentbean实例,所以会getbean(“depentbean”);
getbean(“depentbean”)
发现singletonfactories中已经有depentbean了,则返回depentbean对象
因为depentbean不是工厂bean所以直接返回depentbean
test实例会属性注入depentbean实例成功,test实例初始化ok
depentbean实例会autowired test实例ok
按照这分析先创建depentbean,然后在实例化myfactorybean是可行的,修改xml为如下:
<bean id="dependentbean" class="com.alibaba.test.circle.dependentbean"></bean> <bean id="test" class="com.alibaba.test.circle.myfactorybean"> <property name="depentbean"> <ref bean="dependentbean" /> </property> <property name="name" value="zlx"></property> </bean>
测试运行结果:
name:zlx
hello:zlx
果真可以了,那按照这分析,上面xml配置如果调整了声明顺序,肯定也是会出错的,因为test创建比dependentbean早,测试下果然如此。另外可想而知工厂bean循环依赖工厂bean时候无论声明顺序如何必然也会失败。
3.3 一个思考
上面先注入了myfactorybean中需要使用的dependentbean,然后注入myfactorybean,问题就解决了。那么如果需要在另外一个bean中使用创建的id=”test”的对象时候,这个bean该如何注入那?
类似下面的方式,会成功?留给大家思考^^
public class usetest { @autowired private test test; }
<bean id="usetest" class="com.alibaba.test.circle.usetest"></bean> <bean id="dependentbean" class="com.alibaba.test.circle.dependentbean"></bean> <bean id="test" class="com.alibaba.test.circle.myfactorybean"> <property name="depentbean"> <ref bean="dependentbean" /> </property> <property name="name" value="zlx"></property> </bean>
四、 总结
普通bean之间相互依赖时候bean注入顺序是没有关系的,但是工厂bean与普通bean相互依赖时候则必须先实例化普通bean,这是因为工厂bean的特殊性,也就是其有个getobject方法的缘故。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。