欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-19 14:44:04
一、前言 我们知道 spring 可以是懒加载的,就是当真正使用到 bean 的时候才实例化 bean。当然也不全是这样,例如配置 bean 的 lazy-init...

一、前言

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

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

上一篇:

下一篇: