【问题标题】:Circular dependency in SpringSpring中的循环依赖
【发布时间】:2011-03-29 23:01:18
【问题描述】:

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

【问题讨论】:

标签: java spring


【解决方案1】:

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 尚未完全初始化。

【讨论】:

  • 这就是为什么 Spring 需要一个不带参数的构造函数 ;-)
  • 如果您在 bean 定义中使用构造函数参数,则不会! (但在那种情况下,你不能有循环依赖。)
  • @Richard Fearn 你的帖子是关于问题解释而不是提供解决方案吗?
  • 如果你尝试使用构造函数注入,错误信息是org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    在我正在使用的代码库中(超过 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 参与循环依赖图时,就会发生这种情况。我们通过实现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) 禁用循环依赖解析,以防止将来出现许多问题。

    【讨论】:

    • 有趣的推荐。如果您怀疑循环引用会导致性能问题,我的反建议只会这样做。 (通过尝试解决不需要修复的问题来破坏不需要破坏的东西将是一种耻辱。)
    • 这是一个滑坡到维护地狱以允许循环依赖,从循环依赖重新设计你的架构可能真的很棘手,就像在我们的例子中一样。它对我们的大致意思是,我们在启动期间获得的数据库连接数是循环依赖中涉及的 sessionfactory 的两倍。在其他情况下,由于 bean 被实例化了 12000 多次,可能会发生更灾难性的事情。当然你应该编写你的 bean 以便它们支持销毁它们,但为什么首先允许这种行为呢?
    • @jontejj,你应该得到一个 cookie
    【解决方案4】:

    问题->

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

    【讨论】:

    • 恕我直言,这些不是“解决方案”,而是“解决方法”。 “解决方案”是不创建/具有循环引用。我并没有否定答案作为解决方法的价值。有时你必须使用变通方法。我只是担心(对于未来的读者)“解决方案”这个词的意思是“没关系”。循环引用不是“好的”。
    【解决方案5】:

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

    有什么问题?

    【讨论】:

    • @skaffman 只能使用 after propertiesSet 方法正确使用吗?
    【解决方案6】:

    说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。

    【讨论】:

    • 简单且最好的解释之一。
    【解决方案7】:

    来自Spring Reference

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

    【讨论】:

      【解决方案8】:

      Spring 容器能够解析基于 Setter 的循环依赖,但在基于 Constructor 的循环依赖的情况下会给出运行时异常 BeanCurrentlyInCreationException。 在基于 Setter 的循环依赖的情况下,IOC 容器的处理方式与典型场景不同,后者在注入协作 bean 之前对其进行完全配置。 例如,如果 Bean A 依赖于 Bean B,而 Bean B 依赖于 Bean C,则容器在将 C 注入到 B 之前完全初始化 C,一旦 B 完全初始化,它就会注入到 A。但在循环依赖的情况下,一个bean 在完全初始化之前被注入到另一个。

      【讨论】:

        【解决方案9】:

        它清楚地解释了here。感谢 Eugen Paraschiv。

        循环依赖是一种设计味道,要么修复它,要么使用 @Lazy 来解决导致问题的依赖。

        【讨论】:

          【解决方案10】:

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

          解决方案:

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

          【讨论】:

            【解决方案11】:

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

            【讨论】:

              【解决方案12】:

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

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

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                  猜你喜欢
                  • 2020-07-15
                  • 2017-04-03
                  • 2021-04-24
                  • 1970-01-01
                  • 2015-05-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多