Guice代理支持循环依赖

时间:2012-04-04 15:56:13

标签: java dependency-injection guice

我在启动时的代码中遇到以下错误:

  

尝试代理com.bar.Foo以支持循环依赖,但确实如此   不是界面。

这种代理究竟是如何运作的?如果我只是在接口后面抛出足够的类,一切都会好吗?

(我知道循环依赖通常是代码味道,但我认为在这种情况下它没问题。)

3 个答案:

答案 0 :(得分:10)

虽然“注入接口”方法完全有效,在某些情况下甚至可能是更好的解决方案,但一般来说,您可以使用更简单的解决方案:提供商。

对于每个班级“A”guice可以管理,guice也提供“Provider<A>”。这是javax.inject.Provider接口的内部实现,其get()消息将为“return injector.getInstance(A.class)”。你不必自己实现界面,它是“guice magic”的一部分。

因此,您可以将A-> B,B-A示例缩短为:

public class CircularDepTest {

static class A {
    private final Provider<B> b;
    private String name = "A";

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

static class B {

    private final Provider<A> a;
    private String name = "B";

    @Inject
    public B(Provider<A> a) {
        this.a = a;

    }
}

@Inject
A a;

@Inject
B b;

@Before
public void setUp() {
    Guice.createInjector().injectMembers(this);
}


@Test
public void testCircularInjection() throws Exception {
    assertEquals("A", a.name);
    assertEquals("B", a.b.get().name);
    assertEquals("B", b.name);
    assertEquals("A", b.a.get().name);
}}

我更喜欢这个,因为它更具可读性(你不会因为构造函数已经拥有“B”的实例而感到愚蠢)并且由于你可以自己实现提供者,它仍然可以“手动”工作,在guice context(例如用于测试)。

答案 1 :(得分:9)

我是这个概念的新手,但这是我的理解。

假设您有接口AB,以及实施AiBi

如果Ai依赖BBi依赖A,那么Guice可以创建A的代理实现(调用它Ap)在将来的某个时刻将被赋予Ai代表权。 Guice将Ap提供给Bi,因为它依赖于A,允许Bi完成实例化。然后,由于Bi已经实例化,Guice可以使用Ai实例化Bi。然后,由于Ai现在很好,Guice会告诉Ap委派给Ai

如果AB不是接口(而您只有AiBi),则这是不可能的,因为创建Ap会要求您扩展Ai,这已经需要Bi

以下是代码的外观:

public interface A {
    void doA();
}

public interface B {
    void doB();
}

public class Ai implements A {

   private final B b;

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

   public void doA() {
       b.doB();
   }
}

public class Bi implements B {
   private final A a;

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

   public void doB() {
   }
}

Guice制作的代理类如下所示:

public class Ap implements A {
    private A delegate;
    void setDelegate(A a) {
        delegate = a;
    }

    public void doA() {
        delegate.doA();
    }
}

使用这个基本想法将全部接线:

Ap proxyA = new Ap();
B b = new B(proxyA);
A a = new A(b);
proxyA.setDelegate(a);

如果你只有AiBi,没有接口AB,那就是这样的。

public class Ap extends Ai {
    private Ai delegate;

    public Ap() {
       super(_); //a B is required here, but we can't give one!
    }
}
  

如果我只是在接口后面抛出足够的类,一切都会好吗?

我猜想在构造函数中如何与代理进行交互有严格的限制。换句话说,如果B试图在Guice有机会用真实A填充A的代理之前调用A,那么我会期望一个RuntimeException。

答案 2 :(得分:2)

这是@ jan-galinski的答案,在Scala中重做:

import javax.inject.Inject
import com.google.inject.{Guice, Injector, Provider}
import net.codingwell.scalaguice.InjectorExtensions._

/** Demonstrates the problem by failing with `Tried proxying CircularDep1$A to support a circular dependency, but it is not an interface.
  while locating CircularDep1$A for parameter 0 at CircularDep1$B.<init>(CircularDep.scala:10)
  while locating CircularDep1$B for parameter 0 at CircularDep1$A.<init>(CircularDep.scala:6)
  while locating CircularDep1$A` */
object CircularDep1 extends App {
  class A @Inject() (val b: B) {
    val name = "A"
  }

  class B @Inject() (val a: A) {
    val name = "B"
  }

  val injector: Injector = Guice.createInjector()
  val a: A = injector.instance[A]
  val b: B = injector.instance[B]

  assert("A" == a.name)
  assert("B" == a.b.name)
  assert("B" == b.name)
  assert("A" == b.a.name)
  println("This program won't run!")
}

/** This version solves the problem by using `Provider`s */
object CircularDep2 extends App {
  class A @Inject() (val b: Provider[B]) {
    val name = "A"
  }

  class B @Inject() (val a: Provider[A]) {
    val name = "B"
  }

  val injector: Injector = Guice.createInjector()
  val a: A = injector.instance[A]
  val b: B = injector.instance[B]

  assert("A" == a.name)
  assert("B" == a.b.get.name)
  assert("B" == b.name)
  assert("A" == b.a.get.name)
  println("Yes, this program works!")
}