Spring中的循环依赖

时间:2010-08-14 22:20:20

标签: java spring

Spring如何解决这个问题:bean A依赖于bean B,bean A依赖于bean A.

14 个答案:

答案 0 :(得分:69)

Spring reference manual解释了如何解决循环依赖关系。首先实例化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注入b时,a尚未完全初始化。

答案 1 :(得分:38)

正如其他答案所说,Spring只会处理它,创建bean并根据需要注入它们。

其中一个后果是bean注入/属性设置可能以与XML布线文件似乎暗示的顺序不同的顺序发生。因此,您需要注意,您的属性设置器不会执行依赖于已调用的其他setter的初始化。处理此问题的方法是将bean声明为实现InitializingBean接口。这要求您实现afterPropertiesSet()方法,这是您进行关键初始化的地方。 (我还包括用于检查实际已设置重要属性的代码。)

答案 2 :(得分:17)

在我正在使用的代码库中(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秒。

所以不要总是认为春天可以很好地为你解决这些问题。

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

答案 3 :(得分:12)

它就是这样做的。它实例化ab,并将每个注入另一个(使用他们的setter方法)。

有什么问题?

答案 4 :(得分:8)

问题 - &gt;

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:创建名称为&#39; A&#39;:请求的bean当前正在创建时出错:是否存在无法解析的循环引用?

解决方案1 ​​ - &gt;

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 - &gt;

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 };
}

答案 5 :(得分:6)

来自Spring Reference

  

你通常可以信任Spring   正确的事情。它检测到   配置问题,如   引用不存在的bean和   循环依赖,在容器中   加载时间。 Spring设置属性和   最迟解决依赖关系   可能,当实际上是豆   创建

答案 6 :(得分:6)

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

答案 7 :(得分:5)

明确解释here。感谢Eugen Paraschiv。

循环依赖是一种设计气味,要么修复它,要么使用@Lazy作为依赖项,导致问题解决它。

答案 8 :(得分:4)

说A取决于B,然后Spring首先实例化A,然后B,然后设置B的属性,然后将B设置为A.

但是,如果B还依赖于A?

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

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

对于依赖于构造函数的依赖项,Sprint只是抛出BeanCurrentlyInCreationException来解决这个异常,将bean的lazy-init设置为true,依赖于其他构造函数arg方式。

答案 9 :(得分:3)

如果你通常使用构造函数注入并且不想切换到属性注入,那么Spring的 lookup-method -injection将让一个bean懒惰地查找另一个因此解决了循环依赖。见这里:http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161

答案 10 :(得分:2)

Spring 中的循环依赖:一个 Bean 对另一个 Bean 的依赖。 Bean A → Bean B → Bean A

解决方案:

  1. 使用 @Lazy 注释
  2. 重新设计你的类依赖
  3. 使用 Setter/字段注入
  4. 使用 @PostConstruct 注释

答案 11 :(得分:0)

使用Setter Injection或Field Injection或使用@Lazy作为依赖项。

答案 12 :(得分:0)

如果两个bean彼此依赖,那么我们不应该在两个bean定义中使用Constructor注入。相反,我们必须在任何一个bean中使用setter注入。 (当然我们可以在两个bean定义中使用setter注入,但是抛出'BeanCurrentlyInCreationException'中的构造函数注入

请参阅Spring文档“https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource

答案 13 :(得分:0)

当spring bean之间存在Circular Dependency时,构造函数注入失败。所以在这种情况下,我们Setter注入有助于解决问题。

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