您的位置:首页 > 编程语言 > Java开发

Spring中的循环依赖问题

2016-06-23 16:44 686 查看
最近在研究Spring IOC容器,遇到了对象的循环依赖问题,通过看源码才明白Spring是如何优雅的解决单例循环依赖问题。Spring将对象依赖分成属性依赖和构造依赖,构造依赖问题无法解决,只能抛出BeanCurrentlyInCreationException异常,在解决属性依赖问题,Spring采用的是提前暴露对象的方法。

构造依赖问题:

首先上代码:

有一个类Office

public class Office {

public String name;

public Boss boss;

public Office(String name,Boss boss){
this.name=name;
this.boss=boss;
}
public Boss getBoss() {
return boss;
}

public void setBoss(Boss boss) {
this.boss = boss;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}


public class Boss {
private String name;
private Car car;
private Office office;

/*public Boss(){

}*/
public Boss(String name,Office office){
this.name=name;
this.Office=office;

}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Car getCar() {
return car;
}

public void setCar(Car car) {
this.car = car;
}

public Office getOffice() {
return office;
}

public void setOffice(Office office) {
this.office = office;
}
}


以上两个类存在构造依赖问题,bean.xml配置如下:

<bean id="office" class="org.lww.springTest.Office">
<constructor-arg index="0" value="400工作室"></constructor-arg>
<constructor-arg index="1" ref="boss"></constructor-arg>
</bean>

<bean id="boss" class="org.lww.springTest.Boss">
<constructor-arg index="0" value="Liweiwei"></constructor-arg>
<constructor-arg index="1" ref="office"></constructor-arg>
</bean>


当采用Spring初始时,会抛出异常。我们接下来看看Spring是怎么发现这个异常的。

首先Spring需要预初始化单例对象office,在创建之前,会将office放入到singletonsCurrentlyInCreation集合中,在创建office对象时,发现它有个参数car是引用类型,因此Spring会通过getBean(“car”)去获得car对象,由于car对象尚未创建,则创建car对象,在创建car对象之前,也会将car放入到singletonsCurrentlyInCreation中,实例化car对象时,car构造依赖office,同样也会通过getBean(“office”),由于上一个office对象还未创建成功,这个时候Spring需再重新创建office对象,在创建之前,将office放入到singletonsCurrentlyInCreation时,发现office在singletonsCurrentlyInCreation集合中已经存在了,这个时候Spring判断对象之间出现了循环依赖,那么抛出BeanCurrentlyInCreationException异常。

属性依赖问题:

先上代码:

public class Office {

public String name;

public Boss boss;

public Office(){

}
public Office(String name,Boss boss){
this.name=name;
this.boss=boss;
}
public Boss getBoss() {
return boss;
}

public void setBoss(Boss boss) {
this.boss = boss;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}


public class Boss {
private String name;
private Car car;
private Office office;

/*public Boss(){

}*/
public Boss(String name,Office office){
this.name=name;
this.office=office;

}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Car getCar() {
return car;
}

public void setCar(Car car) {
this.car = car;
}

public Office getOffice() {
return office;
}

public void setOffice(Office office) {
this.office = office;
}
}


下面是bean.xml配置:

<bean id="office" class="org.lww.springTest.Office">
<property name="name" value="400工作室"></property>
<property name="boss" ref="boss"></property>
</bean>

<bean id="boss" class="org.lww.springTest.Boss">
<constructor-arg index="0" value="Liweiwei"></constructor-arg>
<constructor-arg index="1" ref="office"></constructor-arg>
</bean>


大家可以看到,这个时候office和boss类也存在循环依赖关系,但这时候office采用默认构造函数,不依赖于boss对象,这种属于属性依赖,属性依赖Spring可以很好的解决,采用我们上文提到的提前暴露对象。下面分析具体的处理过程。

首先Spring创建office对象,采用其默认构造函数,创建成功后,Spring会通过以下代码将对象提前暴露出来,尽管此时的对象还未完成属性注入,属于早期对象。这个时候对象放在singletonFactories的Map表中,value是函数对象ObjectFactory.

addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});


接下来,Spring会通过函数populateBean来完成office对象的属性注入,再注入boss属性时,发现是一个引用对象,这个时候同样会通过getBean(“boss”)来获得boss对象,boss对象由于从未创建,则创建boss对象。在创建boss对象时,发现它构造依赖于office对象,这个时候Spring也会通过getBean(“office”)获取office对象。由于存在office提前暴露出来,这个时候直接从singletonFactories的Map表中得到office对象并返回,并不需要重新再创建office对象,这样就避免了循环依赖问题,接下来boss对象可以成功被创建,则返回到到office的属性注入中。office属性注入完成后,得到的office对象是成型的,接下来Spring会进一步判断office在后期的处理过程中是否发生引用更改,所谓引用更改就是成型的office对象与早期暴露的office对象是否还是同一个对象。下面是验证代码。

if (earlySingletonExposure) {
//获取指定名称的已注册的单态模式Bean对象
// allowEarlyReference为false,则不会解析singletonFactories
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {//判断点1:首先确定这个对象能从earlySingletonObjects中取出对象来
//根据名称获取的以注册的Bean和正在实例化的Bean是同一个
if (exposedObject == bean) {
//再判断这个对象和当前通过beanPostProcessor处理过的对象是否相同,如果相同,表示对象没有经过修改,即A=A-,那么循环引用成立。无需处理
exposedObject = earlySingletonReference;
}
//当前Bean依赖其他Bean,并且当发生循环引用时不允许新创建实例对象
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {//出现循环引用,且被引用的bean被修改
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}


如果是同一个引用对象,则循环引用成立,否则会抛出BeanCurrentlyInCreationException异常,大家可看到异常消息:

Bean with name ’ beanName ’ has been injected into other beans in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using ‘getBeanNamesOfType’ with the ‘allowEagerInit’ flag turned off, for example.

大意是:当前对象的早期版本被注入到其他对象引用中,也就是最终版本和原始版本不一样导致的,这个时候Spring只能抛出异常。

好了,Spring的循环依赖处理过程就这些了,如果有什么错误,欢迎指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  循环依赖