【问题标题】:Handle circular dependency in CDI在 CDI 中处理循环依赖
【发布时间】:2015-07-06 00:44:08
【问题描述】:

我有这样的情况。我看不到任何错误,但我没有得到我的结果。

@ApplicationScoped
public class A {

    private B b;


    @Inject
    public A(B b) {
        this.b = b;
    }
}

@Singleton
public class B {

    private A a;


    @Inject
    public B(A a) {
        this.a = a;
    }
}

这种依赖注入有错吗?

谁能帮帮我。

【问题讨论】:

  • 你需要重构,把依赖的代码从这些A类和B类中移开,并创建一个C类。
  • @André。谢谢回复。你能说出这个 A 和 B 是如何从 C 调用的吗
  • CDI 不能处理循环依赖,EJB 可以
  • @maress,是的,它可以,但我认为这是一种代码味道,具有循环依赖。他要么重构,要么开始使用 EJB。
  • 你没有提到你的环境。 Weld 2.2.11.Final 给了我一个非常明确的错误信息:WELD-001410: The injection point [BackedAnnotatedParameter] Parameter 1 of [BackedAnnotatedConstructor] @Inject public demo.B(A) has non-proxyable dependencies

标签: java dependency-injection cdi


【解决方案1】:

我会避免这种循环依赖,这样做有几个原因。

评论this article

凌乱的构造函数是一个标志。它警告我,我的班级正在变成一个庞然大物,是一个多面手,一无所有。换句话说,一个凌乱的构造函数实际上是一件好事。如果我觉得一个类的构造函数太乱了,我就知道是时候做点什么了。

还有this one

您会发现类 A 需要 B 的实例而 B 需要 A 的实例的情况。这是循环依赖的典型情况,显然是不好的。以我的经验,解决方案是要么使 B 成为 A 的一部分,当两者如此强烈地依赖以至于它们真的应该是一个类时。更常见的情况是,至少还有一个 C 类隐藏在那里,因此 B 不需要 A 而只需要 C。

作为奥利弗·格克commented:

尤其是构造函数注入实际上可以防止你引入循环依赖。如果您确实介绍了它们,那么您实际上将两方合而为一,因为您不能真正改变一方而不冒破坏另一方的风险,这在任何情况下都是一种设计味道。

这是我可能会做的一个小例子。

public class A {

    private B b;

    @Autowired
    public A(B b) {
        this.b = b;
    }

    public void doSomeWork() {
        // WORK
    }

    public void doSomeWorkWithB() {
        b.doSomeWork();
    }
}

public class B {

    private A a;

    @Autowired
    public B(A a) {
        this.a = a;
    }

    public void doSomeWork() {
        // WORK
    }

    public void doSomeWorkWithA() {
        a.doSomeWork();
    }

}

重构后可能是这个样子。

public class A {

    private C c;

    @Autowired
    public A(C c) {
        this.c = c;
    }

    public void doSomeWork() {
        // WORK
    }

    public void doSomeWorkWithC() {
        c.doSomeWorkThatWasOnA();
    }

}

public class B {

    private C c;

    @Autowired
    public B(C c) {
        this.c = c;
    }

    public void doSomeWork() {
        // WORK
    }

    public void doSomeWorkWithC() {
        c.doSomeWorkThatWasOnB();
    }

}

public class C {

    public void doSomeWorkThatWasOnB() {
        // WORK

    }

    public void doSomeWorkThatWasOnA() {
        // WORK
    }

}

【讨论】:

  • 您可以在我发布的链接上进行大量阅读,祝您阅读和编码愉快! :D
  • 非常感谢。你能告诉我如何从 C 中调用一些方法吗?还是我必须复制那里的所有逻辑?
  • 你重构了 A 和 B 的代码,所以没有从 A 到 B 和 B 到 A 的调用,将逻辑移动到 C
  • 问题是关于 CDI,而不是关于 Spring 或一般的依赖注入。我同意应该避免循环依赖,但你的回答并没有真正解决这个问题。
  • 感谢您的意见@hwellmann,如果我得出相同的结论,我会调查 CDI 并删除答案
【解决方案2】:

引用CDI Specification 1.2 的第 5 节:

容器需要支持 bean 中的循环 至少有一个 bean 参与其中的依赖关系图 循环依赖链具有正常范围,如 普通作用域和伪作用域。容器不需要 支持每个 bean 参与的依赖循环链 在链中有一个伪作用域。

ApplicationScoped 是一个正常的范围,所以这个循环应该可以工作。

在您的示例中,类 A 无法代理,因为它缺少零参数构造函数。添加此构造函数(可能具有受保护或包可见性),您的示例部署没有问题。

【讨论】:

  • 这正确解决了问题,如果类 A 丢失它是 Patan 源的无参数构造函数。
【解决方案3】:

您也可以使用基于 Setter 的依赖注入来解决此问题。

【讨论】:

  • 如果通过构造函数进行依赖注入,ClassA 需要 ClassB,ClassB 需要 ClassA 进行实例化,这会产生循环依赖。如果使用基于 setter 的 DI,classA 或 ClassB 会先被实例化,然后实例化的对象用于设置彼此的属性,从而打破循环依赖。
  • 我想稍微扩展一下这个答案,因为我发现它很有用。您可以使用@PostConstruct,使用 setter 手动设置依赖项,因此您可以拥有基于 setter 的依赖注入
【解决方案4】:

这肯定有解决办法。让我quote我自己:

正确的解决方案是注入 javax.enterprise.inject.Instance,其中 T 是要注入的类的类型。由于类型直接为 Foo,因此在类型为 Instance 的对象上调用 get() 方法可以保证始终注入正确的对象。这种方法效果很好,因为实例是由实现本身从容器中动态获取的,并且仅在需要时才获取。因此,依赖项检索的责任留给您的代码 - 您的代码有责任不进行无限循环。

 @Named
public class Foo implements Fooable{

@Inject
private Instance<Foo> foo;

public void executeFirst(){
foo.get().executeSecond();
}

@Transactional
public void executeSecond(){
//do something
}

}

【讨论】:

    猜你喜欢
    • 2011-07-26
    • 2012-08-10
    • 2014-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-02
    • 1970-01-01
    • 2012-06-07
    相关资源
    最近更新 更多