春天的循环依赖


Answers:


42

就像其他答案所说的那样,Spring会处理它,创建bean并根据需要注入它们。

结果之一是,bean注入/属性设置可能以与XML接线文件所暗示的顺序不同的顺序发生。因此,您需要注意,属性设置器不要进行依赖已被调用的其他设置器的初始化。解决此问题的方法是将bean声明为实现InitializingBean接口。这需要您实现该afterPropertiesSet()方法,并且在此处进行关键的初始化。(我还包含用于检查重要属性是否已实际设置的代码。)


76

Spring参考手册介绍了循环依赖是如何解决的。首先实例化这些bean,然后将它们相互注入。

考虑此类:

package mypackage;

public class A {

    public A() {
        System.out.println("Creating instance of A");
    }

    private B b;

    public void setB(B b) {
        System.out.println("Setting property b of A instance");
        this.b = b;
    }

}

和一个类似的类B

package mypackage;

public class B {

    public B() {
        System.out.println("Creating instance of B");
    }

    private A a;

    public void setA(A a) {
        System.out.println("Setting property a of B instance");
        this.a = a;
    }

}

如果随后有了此配置文件:

<bean id="a" class="mypackage.A">
    <property name="b" ref="b" />
</bean>

<bean id="b" class="mypackage.B">
    <property name="a" ref="a" />
</bean>

使用此配置创建上下文时,将看到以下输出:

Creating instance of A
Creating instance of B
Setting property a of B instance
Setting property b of A instance

请注意,当a注入时ba尚未完全初始化。


26
这就是为什么Spring需要没有参数的构造函数;-)
克里斯·汤普森

15
如果在bean定义中使用构造函数参数,则不行!(但在这种情况下,您不能具有循环依赖关系。)
理查德·费恩

1
@Richard Fearn您的帖子是有关问题的说明而不是解决方案的吗?
gstackoverflow

4
如果您尝试使用构造器注入,该错误信息是org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
X. Satuk禾

19

在我正在使用的代码库(100万行代码)中,我们遇到了启动时间较长(大约60秒)的问题。我们得到了12000+ FactoryBeanNotInitializedException

我所做的是在AbstractBeanFactory#doGetBean中设置了条件断点

catch (BeansException ex) {
   // Explicitly remove instance from singleton cache: It might have been put there
   // eagerly by the creation process, to allow for circular reference resolution.
   // Also remove any beans that received a temporary reference to the bean.
   destroySingleton(beanName);
   throw ex;
}

destroySingleton(beanName)我在哪里用条件断点代码打印异常:

   System.out.println(ex);
   return false;

FactoryBean包含在循环依赖图中时,显然会发生这种情况。我们通过实现ApplicationContextAwareInitializingBean并手动注入Bean 来解决了它。

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class A implements ApplicationContextAware, InitializingBean{

    private B cyclicDepenency;
    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        ctx = applicationContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        cyclicDepenency = ctx.getBean(B.class);
    }

    public void useCyclicDependency()
    {
        cyclicDepenency.doSomething();
    }
}

这将启动时间减少到大约15秒。

因此,不要总是认为spring可以很好地为您解决这些参考。

出于这个原因,我建议使用AbstractRefreshableApplicationContext#setAllowCircularReferences(false)禁用循环依赖关系解析,以防止将来出现许多问题。


3
有趣的建议。我的反建议仅在您怀疑循环引用导致性能问题时才这样做。(通过尝试解决不需要解决的问题来破坏不需要解决的问题是可耻的。)
Stephen C

2
允许维护循环依赖关系到维护地狱都是一个滑坡,从循环依赖关系重新设计您的体系结构确实很棘手,就像我们的情况一样。这对我们的大致含义是,启动期间获得的数据库连接是会话工厂所涉及的循环依赖的两倍。在其他情况下,由于bean被实例化12000+次,可能会发生更多灾难性的事情。当然,您应该编写自己的bean,以便它们支持销毁它们,但是为什么首先要允许这种行为?
jontejj 2013年

@jontejj,您应该得到一个cookie
serprime

14

问题->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(B b) { this.b = b };
}


Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
 }

//原因:org.springframework.beans.factory.BeanCurrentlyInCreationException:创建名称为'A'的bean时出错:当前正在创建请求的bean:是否存在不可解析的循环引用?

解决方案1->

Class A {
    private B b; 
    public A( ) {  };
    //getter-setter for B b
}

Class B {
    private A a;
    public B( ) {  };
    //getter-setter for A a
}

解决方案2->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(@Lazy B b) { this.b = b };
}

Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
}

12

就是这样。它实例化ab,并将彼此注入(使用其setter方法)。

有什么问题?


9
@javaguy:不,不会。
skaffman's

@skaffman只有在propertiesSet方法使用正确之后才能使用?
gstackoverflow

6

Spring参考

通常,您可以信任Spring做正确的事。它在容器加载时检测配置问题,例如对不存在的Bean的引用和循环依赖项。在实际创建Bean时,Spring设置属性并尽可能晚地解决依赖关系。


6

Spring容器能够解析基于Setter的循环依赖关系,但在基于构造函数的循环依赖关系的情况下,会给出运行时异常BeanCurrentlyInCreationException。在基于Setter的循环依赖性的情况下,IOC容器与典型方案的处理方式不同,在典型方案中,它将在注入协作bean之前对其进行完全配置。例如,如果Bean A对Bean B和Bean B在Bean C上具有依赖性,则容器在将C注入到B之前对其进行完全初始化,并且一旦B完全初始化就将其注入到A。但是在循环依赖性的情况下,在完全初始化之前,将Bean中的Bean注入另一个。


5

假设A依赖于B,那么Spring将首先实例化A,然后实例化B,然后为B设置属性,然后将B设置为A。

但是,如果B也依赖于A怎么办?

我的理解是:Spring刚刚发现A已经被构造(构造函数执行),但是还没有完全初始化(并不是所有注入都完成了),嗯,它认为,没关系,A可以被完全初始化是可以容忍的,只要将其设置为-现在将A实例完全初始化为B。在B完全初始化之后,将其设置为A,最后,A现已完全启动。

换句话说,它只是提前将A暴露给B。

对于通过构造函数的依赖关系,Sprint只是抛出BeanCurrentlyInCreationException,以解决此异常,请通过构造函数-arg方式将依赖于其他bean的bean的lazy-init设置为true。


简单而最好的解释之一。
斯里坦贾加杰夫'18

5

在这里清楚地解释。感谢Eugen Paraschiv。

循环依赖关系是一种设计气味,可以对其进行修复,也可以使用@Lazy作为依赖关系,这会导致问题解决。



3

当spring bean之间存在循环依赖关系时,构造函数注入失败。因此,在这种情况下,我们的Setter注入有助于解决问题。

基本上,构造函数注入对于强制性依赖项很有用,对于可选依赖项,最好使用Setter注入,因为我们可以进行重新注入。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.