处理 CDI 中的循环依赖

Handle circular dependency in CDI

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

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

这种依赖注入是不是错了?

谁能帮我解决这个问题。

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

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

评论this article

A messy constructor is a sign. It warns me that my class is becoming a monolith which is a jack of all trades and a master of none. In other words, a messy constructor is actually a good thing. If I feel that the constructor of a class is too messy, I know that it is time to do something about it.

this one

You’ll find cases where a class A needs an instance of B and B needs an instance of A. This is a typical case of a circular dependency and is obviously bad. In my experience the solution is either to make B a part of A when the two are so strongly dependent that they really should be one class. More often though there is at least one more class C hiding in there so that B doesn’t need A but only C.

作为奥利弗·格克 commented:

Especially constructor injection actually prevents you from introducing cyclic dependencies. If you do introduce them you essentially make the two parties one because you cannot really change the one without risking to break the other, which in every case is a design smell.

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

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
    }

}

引自 CDI Specification 1.2 的第 5 节:

The container is required to support circularities in the bean dependency graph where at least one bean participating in every circular chain of dependencies has a normal scope, as defined in Normal scopes and pseudo-scopes. The container is not required to support circular chains of dependencies where every bean participating in the chain has a pseudo-scope.

ApplicationScoped 是一个正常的范围,所以这个循环应该有效。

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

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

正确的解决方案是注入 javax.enterprise.inject.Instance,其中 T 是要注入的 class 的类型。由于类型直接是 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
}

}