起動時にコードで次のエラーが発生します。
循環依存関係をサポートするために com.bar.Foo をプロキシしようとしましたが、それはインターフェイスではありません。
このプロキシはどのように機能しますか? インターフェイスの背後に十分なクラスを投げるだけで、すべてうまくいくでしょうか?
(通常、循環依存はコードのにおいがすることはわかっていますが、この場合は問題ないと思います。)
起動時にコードで次のエラーが発生します。
循環依存関係をサポートするために com.bar.Foo をプロキシしようとしましたが、それはインターフェイスではありません。
このプロキシはどのように機能しますか? インターフェイスの背後に十分なクラスを投げるだけで、すべてうまくいくでしょうか?
(通常、循環依存はコードのにおいがすることはわかっていますが、この場合は問題ないと思います。)
「インターフェイスを挿入する」アプローチは完全に有効であり、場合によってはより良いソリューションになることもありますが、一般的には、より単純なソリューションであるプロバイダーを使用できます。
guice が管理できるすべてのクラス "A" に対して、guice は " Provider<A>
" も提供します。これは javax.inject.Provider インターフェースの内部実装であり、そのget()
メッセージは " return injector.getInstance(A.class)
" になります。「魔法の魔法」の一部であるインターフェイスを自分で実装する必要はありません。
したがって、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
、Guiceは( と呼ぶ)A
のプロキシ実装を作成できます。これは、将来のある時点で委譲先が与えられます。Guice は、への依存性のためにそれを提供し、インスタンス化を完了できるようにします。次に、がインスタンス化されているので、Guice は でインスタンス化できます。すると、は良いことになったので、Guice はに委譲するように指示します。A
Ap
Ai
Ap
Bi
A
Bi
Bi
Ai
Bi
Ai
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!
}
}
インターフェイスの背後に十分なクラスを投げるだけで、すべてうまくいくでしょうか?
コンストラクターでプロキシを操作する方法には厳しい制限があると思います。つまり、Guice が A のプロキシに実際の A を設定する前に B が 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!")
}