天天看点

Spring中的循环依赖问题

最近在研究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的循环依赖处理过程就这些了,如果有什么错误,欢迎指正。

继续阅读