2012-04-04 13 views
8

sto ottenendo il seguente errore nel mio codice al momento del lancio:Guice proxy per sostenere la dipendenza circolare

provato proxying com.bar.Foo per sostenere una dipendenza circolare, ma non è un'interfaccia.

Come funziona esattamente questo proxy? Se metto abbastanza lezioni dietro le interfacce, andrà tutto bene?

(so che le dipendenze circolari sono di solito un odore di codice, ma credo che in questo caso è ok.)

risposta

6

Sono nuovo di questo concetto, ma qui è la mia comprensione.

Diciamo che avete interfacce A e B, e implementazioni Ai e Bi.

Se Ai ha una dipendenza B, e Bi ha una dipendenza A, allora Guice può creare un'implementazione procura di A (lo chiamano Ap) che ad un certo punto in futuro essere dato un Ai di delegare al. Guice fornisce Ap a Bi per la sua dipendenza su A, consentendo a Bi di terminare l'istanziazione. Quindi, dal momento che Bi è stato istanziato, Guice può creare un'istanza Ai con Bi. Quindi, dal momento che Ai è ora buono da fare, Guice dice a Ap di delegare a Ai.

Se A e B non erano interfacce (e basta avuto Ai e Bi) questo proprio non sarebbe possibile, perché la creazione di Ap richiederebbe di estendere la Ai, che ha bisogno di un già Bi.

Qui è quello che potrebbe essere simile con il codice:

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

Il proxy che Guice fa sarebbe simile a questa:

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

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

E tutto sarebbe cablata utilizzando questa idea di base:

Ap proxyA = new Ap(); 
B b = new B(proxyA); 
A a = new A(b); 
proxyA.setDelegate(a); 

Ed ecco come sarebbe se avessi solo Ai e Bi, senza interfacce A e B.

public class Ap extends Ai { 
    private Ai delegate; 

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

Se ho solo un tiro abbastanza classi dietro le interfacce, sarà tutto bene?

Direi che esistono restrizioni rigorose su come il proxy può essere interagito con il costruttore. In altre parole, se B prova a chiamare A prima che Guice abbia avuto la possibilità di popolare il proxy di A con la vera A, allora mi aspetterei una RuntimeException.

7

Mentre l'approccio "iniettare un'interfaccia" è totalmente valido e potrebbe persino essere la soluzione migliore in alcune occasioni, in generale, è possibile utilizzare una soluzione più semplice: i provider.

Per ogni classe guiz può gestire, guice offre anche uno "Provider<A>". Questa è un'implementazione interna dell'interfaccia javax.inject.Provider, il cui messaggio get() sarà "return injector.getInstance(A.class)". Non è necessario implementare l'interfaccia da soli, fa parte della "magia guizza".

questo modo è possibile accorciare la A-> B, ad esempio BA a:

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); 
}} 

preferisco questo, perché il suo più leggibile (non si è ingannati a credere che il costruttore è già titolare di un caso di "B ") e dal momento che è possibile implementare i Provider da soli, funzionerebbe comunque" a mano ", al di fuori del contesto guice (per testare ad esempio).

+0

Anch'io preferisco questo approccio. Significa che non devi creare interfacce quando altrimenti non ne avresti bisogno. È anche più veloce/meno potenzialmente disordinato cambiare l'iniezione in un Provider piuttosto che creare interfacce. – specialtrevor

+0

Che ne dici di avere una dipendenza esplicita da 'Guice' nel tuo codice app? Ho sempre pensato che fosse bello rimanere indipendenti dal framework DI. Prima di introdurre 'Provider' nel tuo codice potresti passare a dire' Spring' ma questo non è più possibile. –

+0

Sto parlando di javax.inject.Provider , un'interfaccia standard JSR330. Non è richiesta alcuna dipendenza da guiz. –

2

Ecco @ risposta di Jan-Galinski, rifatti a 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!") 
} 
Problemi correlati