当前位置: 移动技术网 > IT编程>开发语言>Java > Spring循环依赖正确性及Bean注入的顺序关系详解

Spring循环依赖正确性及Bean注入的顺序关系详解

2019年07月19日  | 移动技术网IT编程  | 我要评论

一、前言

我们知道 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方法的缘故。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网