2012-04-04 95 views
8

我得到我的代碼下面的錯誤在發射:吉斯代理支持循環依賴

試圖進行代理com.bar.Foo支持循環依賴,但它是不是 的接口。

這個代理工作到底如何?如果我只是在接口後面拋出足夠的類,一切都會好起來嗎?

(我知道,循環依賴通常是一個代碼味道,但我認爲在這種情況下,它的確定。)

回答

6

我是新來這個概念,但這裏是我的理解。

假設你有接口AB,以及實現AiBi

如果AiB的依賴,並BiA的依賴,那麼吉斯可以創建A代理實現(稱之爲Ap),將在未來的某個時刻被給予Ai委派。 Guice給出ApBi,因爲它依賴於A,允許Bi完成實例化。然後,由於Bi已被實例化,Guice可以用Bi實例化Ai。然後,由於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() { 
    } 
} 

的代理類,吉斯使得看起來像這樣:

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。

7

雖然「注入接口」方法是完全有效的,並且在某些情況下甚至可能是更好的解決方案,但通常情況下,您可以使用更簡單的解決方案:提供者。

對於Guice可以管理的每個班級「A」,guice還提供「Provider<A>」。這是javax.inject.Provider接口的內部實現,其接口消息將爲「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); 
}} 

我喜歡這個,因爲它更具有可讀性(你是不是上當beleive,構造函數已經持有B的」一個實例「),並且由於您可以自己實現提供者,所以它仍然可以在guice環境之外」手工「工作(例如,用於測試)。

+0

我也喜歡這種方式。這意味着當你不需要它們時,你不必創建接口。與創建接口相比,將注入更改爲提供者也更快/更不可能。 – specialtrevor 2013-11-19 16:30:50

+0

如何在你的應用程序代碼中顯式依賴'Guice'?我一直認爲獨立於DI框架是很好的。在你將'Provider'引入你的代碼之前,你可能已經轉向說'Spring'了,但這是不可能的。 – 2015-01-19 09:30:52

+0

我正在談論一個JSR330標準接口javax.inject.Provider 。不需要guice依賴。 – 2015-01-19 12:44:40

2

這裏是@揚加林斯基的回答,在斯卡拉重做:

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!") 
}