【发布时间】:2011-03-29 23:01:18
【问题描述】:
Spring 如何解决这个问题:bean A 依赖于 bean B,而 bean B 依赖于 bean A。
【问题讨论】:
-
另一篇解释循环依赖如何发生的有用文章:octoperf.com/blog/2018/02/15/spring-circular-dependencies
Spring 如何解决这个问题:bean A 依赖于 bean B,而 bean B 依赖于 bean A。
【问题讨论】:
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 尚未完全初始化。
【讨论】:
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
正如其他答案所说,Spring 只是处理它,创建 bean 并根据需要注入它们。
其中一个后果是 bean 注入/属性设置的发生顺序可能与您的 XML 连接文件所暗示的顺序不同。因此,您需要注意您的属性设置器不会进行依赖于已调用的其他设置器的初始化。处理这个问题的方法是将 bean 声明为实现 InitializingBean 接口。这需要您实现afterPropertiesSet() 方法,这是您进行关键初始化的地方。 (我还包括检查重要属性是否已实际设置的代码。)
【讨论】:
在我正在使用的代码库中(超过 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;
显然,当FactoryBeans 参与循环依赖图时,就会发生这种情况。我们通过实现ApplicationContextAware 和InitializingBean 并手动注入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) 禁用循环依赖解析,以防止将来出现许多问题。
【讨论】:
问题->
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 };
}
【讨论】:
它就是这么做的。它实例化a 和b,并将每一个注入到另一个中(使用它们的setter 方法)。
有什么问题?
【讨论】:
说A依赖B,那么Spring会先实例化A,再实例化B,然后为B设置属性,再将B设置到A中。
但是如果 B 也依赖于 A 呢?
我的理解是:Spring刚刚发现A已经构造好了(构造函数执行了),但是没有完全初始化(不是所有的注入都完成了),嗯,它想,没关系,可以容忍A没有完全初始化,只是设置这个未完全初始化的 A 实例现在进入 B。 B完全初始化后,设置到A中,最后A现在完全初始化了。
也就是说,它只是提前将 A 暴露给 B。
对于通过constructor-arg方式依赖的bean,Sprint只是抛出BeanCurrentlyInCreationException,为了解决这个异常,将通过constructor-arg方式依赖于其他bean的bean的lazy-init设置为true。
【讨论】:
您通常可以信任 Spring 来做 正确的事情。它检测到 配置问题,例如 对不存在的 bean 的引用和 循环依赖,在容器中 加载时间。 Spring 设置属性和 解决依赖关系最晚 可能,当 bean 实际上是 已创建。
【讨论】:
Spring 容器能够解析基于 Setter 的循环依赖,但在基于 Constructor 的循环依赖的情况下会给出运行时异常 BeanCurrentlyInCreationException。 在基于 Setter 的循环依赖的情况下,IOC 容器的处理方式与典型场景不同,后者在注入协作 bean 之前对其进行完全配置。 例如,如果 Bean A 依赖于 Bean B,而 Bean B 依赖于 Bean C,则容器在将 C 注入到 B 之前完全初始化 C,一旦 B 完全初始化,它就会注入到 A。但在循环依赖的情况下,一个bean 在完全初始化之前被注入到另一个。
【讨论】:
它清楚地解释了here。感谢 Eugen Paraschiv。
循环依赖是一种设计味道,要么修复它,要么使用 @Lazy 来解决导致问题的依赖。
【讨论】:
Spring 中的循环依赖:一个 Bean 对另一个 Bean 的依赖。 Bean A → Bean B → Bean A
解决方案:
@Lazy注解@PostConstruct注解【讨论】:
如果您通常使用构造函数注入并且不想切换到属性注入,那么 Spring 的 lookup-method-注入将使一个 bean 懒惰地查找另一个 bean,从而解决循环依赖关系。见这里:http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161
【讨论】:
当 spring bean 之间存在循环依赖时,构造函数注入失败。所以在这种情况下,我们的 Setter 注入有助于解决问题。
基本上,构造函数注入对于强制依赖项很有用,对于可选依赖项更好地使用 Setter 注入,因为我们可以进行重新注入。
【讨论】:
如果两个 bean 相互依赖,那么我们不应该在两个 bean 定义中都使用构造函数注入。相反,我们必须在任何一个 bean 中使用 setter 注入。 (当然,我们可以在两个 bean 定义中都使用 setter 注入,但是两者中的构造函数注入都会抛出 'BeanCurrentlyInCreationException'
请参阅 Spring 文档“https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource”
【讨论】: