2012-04-04 13 views
8

Dostaję następujący błąd w moim kodu na szalupie:Guice proxy do obsługi kołową zależność

Wypróbowywany buforowania com.bar.Foo wspierać kołową zależność, ale nie jest to interfejs.

Jak dokładnie działa to proxy? Jeśli po prostu wyrzucę wystarczającą liczbę klas za interfejsy, czy wszystko będzie dobrze?

(wiem, że okrągłe zależności są zazwyczaj zapach kod, ale myślę, że w tym przypadku jest to ok.)

Odpowiedz

6

jestem nowy w tej koncepcji, ale tutaj jest moje zrozumienie.

Załóżmy, że masz interfejsy A i B oraz implementacje Ai i Bi.

Jeśli Ai ma zależność B i Bi ma zależność A, następnie Guice może stworzyć realizację proxy A (nazywają to Ap), które w pewnym momencie w przyszłości zostać podany jest Ai powierzyć. Guice podaje, że Ap do jest zależny od A, umożliwiając zakończenie instancji Bi. Następnie, odkąd utworzono instancję Bi, Guice może utworzyć instancję Ai z Bi. Następnie, od Ai jest teraz dobrze zrobić, Guice mówi Ap, aby delegować do Ai.

Jeśli A i B nie były interfejsy (i po prostu trzeba Ai i Bi) to po prostu nie byłoby możliwe, gdyż wymagałoby tworzenia Ap Ci przedłużyć Ai, który już potrzebuje Bi.

Oto, co to może wyglądać z kodem:

klasy
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() { 
    } 
} 

Pełnomocnik że Guice sprawia będzie wyglądać następująco:

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

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

I to wszystko być podłączone przy użyciu tej podstawowej idei:

A oto, jak by to było, gdybyś miał tylko Ai i Bi, bez interfejsów A i B.

public class Ap extends Ai { 
    private Ai delegate; 

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

Gdybym po prostu wyrzucić za sobą wystarczającą ilość interfejsów klas, będzie wszystko w porządku?

Przypuszczam, że istnieją ścisłe ograniczenia dotyczące sposobu interakcji proxy w konstruktorze. Innymi słowy, jeśli B spróbuje wywołać A zanim Guice ma szansę zaludnić proxy A z prawdziwym A, wówczas oczekiwałbym wyjątku RuntimeException.

7

Chociaż podejście "wstrzyknij interfejs" jest całkowicie poprawne, a nawet może być lepszym rozwiązaniem w niektórych przypadkach, ogólnie można zastosować prostsze rozwiązanie: Dostawcy.

Dla każdej klasy "A" guice może zarządzać, guice oferuje również "Provider<A>". Jest to wewnętrzna implementacja interfejsu javax.inject.Provider, którego wiadomość get() będzie "return injector.getInstance(A.class)". Nie musisz sam implementować interfejsu, jego część "magii guice".

W ten sposób można skrócić A-> B, przykładem 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); 
}} 

wolę to, ponieważ jest to bardziej czytelne (nie są nabrać do uwierzyć, że konstruktor posiada już instancję „B ") i skoro możesz sam implementować Dostawców, to nadal działa" ręcznie ", poza kontekstem guice (na przykład do testowania).

+0

ja też wolą tego podejścia. Oznacza to, że nie musisz tworzyć interfejsów, gdy nie będziesz ich potrzebować. Jest także szybsze/mniej potencjalnie niechlujne, aby zmienić wtrysk do dostawcy niż tworzyć interfejsy. – specialtrevor

+0

Co powiesz na wyraźną zależność od 'Guice' w twoim kodzie aplikacji? Zawsze myślałem, że miło jest pozostać niezależnym od frameworka DI. Zanim wprowadziłeś 'Provider' do swojego kodu, mógłbyś przełączyć się na" Spring ", ale nie jest to już możliwe. –

+0

Mówię o javax.inject.Provider , standardowym interfejsie JSR330. Nie jest wymagana zależność od guice. –

2

Oto odpowiedź @ Jan-Galiński'S, przerobione w 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!") 
}