Guice代理支持循环依赖
我在启动时的代码中遇到以下错误:
尝试代理com.bar.Foo来支持循环依赖,但它不是一个接口。
这种代理究竟是如何工作的? 如果我只是在接口后面抛出足够的类,一切都会好吗?
(我知道循环依赖通常是代码味道,但我认为在这种情况下它是可以的。)
我是这个概念的新手,但这是我的理解。
假设你有接口A
和B
,以及实现Ai
和Bi
。
如果Ai
依赖于B
,并且Bi
依赖于A
,那么Guice可以创建A
的代理实现(称之为Ap
),该代理实现将在将来的某个时刻被授予Ai
委托给。 Guice将Ap
指向Bi
因为它依赖于A
,允许Bi
完成实例化。 然后,由于Bi
已被实例化,Guice可以用Bi
实例化Ai
。 然后,由于Ai
现在很好,Guice告诉Ap
委托给Ai
。
如果A
和B
不是接口(并且你只有Ai
和Bi
),这就不可能了,因为创建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);
如果您只有Ai
和Bi
,没有接口A
和B
。
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。
虽然“注入接口”方法是完全有效的,甚至在某些情况下甚至可能是更好的解决方案,但一般来说,您可以使用更简单的解决方案:提供商。
对于每个“A”级别的guice可以管理,guice还提供“ Provider
”。 这是javax.inject.Provider接口的内部实现,其get()
消息将“ return injector.getInstance(A.class)
”。 你不必自己实现界面,它是“guice magic”的一部分。
因此,您可以将A-> B,BA示例缩短为:
public class CircularDepTest { static class A { private final Provider b; private String name = "A"; @Inject public A(Provider b) { this.b = b; } } static class B { private final Provider a; private String name = "B"; @Inject public B(Provider 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", abget().name); assertEquals("B", b.name); assertEquals("A", baget().name); }}
我更喜欢这个,因为它更具可读性(你不会因为构造函数已经拥有“B”的实例而感到愚蠢)并且由于你可以自己实现提供者,它仍然可以在guice上下文之外“手动”工作(用于测试例如)。
这是@ 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.(CircularDep.scala:10) while locating CircularDep1$B for parameter 0 at CircularDep1$A. (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" == abname) assert("B" == b.name) assert("A" == baname) 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" == abget.name) assert("B" == b.name) assert("A" == baget.name) println("Yes, this program works!") }