启动时我的代码中出现以下错误:
尝试代理 com.bar.Foo 以支持循环依赖,但它不是接口。
这个代理究竟是如何工作的?如果我只是在接口后面抛出足够多的类,一切都会好吗?
(我知道循环依赖通常是一种代码味道,但我认为在这种情况下没关系。)
启动时我的代码中出现以下错误:
尝试代理 com.bar.Foo 以支持循环依赖,但它不是接口。
这个代理究竟是如何工作的?如果我只是在接口后面抛出足够多的类,一切都会好吗?
(我知道循环依赖通常是一种代码味道,但我认为在这种情况下没关系。)
虽然“注入接口”方法是完全有效的,在某些情况下甚至可能是更好的解决方案,但通常,您可以使用更简单的解决方案:Providers。
对于每个“A”类 guice 可以管理,guice 还提供了一个“ Provider<A>
”。这是 javax.inject.Provider 接口的内部实现,其get()
消息将“ return injector.getInstance(A.class)
”。您不必自己实现接口,它是“guice 魔法”的一部分。
因此,您可以将 A->B, BA 示例缩短为:
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 上下文之外“手动”工作(例如用于测试)。
我对这个概念很陌生,但这是我的理解。
假设你有接口A
和B
,和实现Ai
和Bi
。
如果Ai
对 有依赖关系B
,并且Bi
对 有依赖关系A
,那么 Guice 可以创建一个代理实现A
(称为它Ap
),它将在将来的某个时候被赋予一个Ai
委托给。Guice 给出Ap
了Bi
它对 的依赖A
,允许Bi
完成实例化。然后,既然Bi
已经实例化了,Guice 就可以Ai
用Bi
. 然后,既然Ai
现在可以做,Guice 告诉Ap
委派给Ai
.
如果A
andB
不是接口(而你只是有Ai
and 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
and 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。
这是@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!")
}