2012-04-04 13 views
8

Estoy recibiendo el siguiente error en mi código en el lanzamiento:Guice proxy para apoyar dependencia circular

Probamos proxy com.bar.Foo para apoyar una dependencia circular, pero es no una interfaz.

¿Cómo funciona exactamente este proxying? Si solo lanzo suficientes clases detrás de las interfaces, ¿todo estará bien?

(Sé que las dependencias circulares son generalmente un olor código, pero creo que en este caso está bien.)

Respuesta

6

soy nuevo en este concepto, pero aquí está mi entendimiento.

Digamos que tiene interfaces A y B, y las implementaciones Ai y Bi.

Si Ai tiene una dependencia en B y Bi tiene una dependencia en A, a continuación, Guice puede crear una aplicación de proxy de A (llámese Ap) que en algún momento en el futuro ser objeto de una Ai delegar a. Guice da ese Ap a Bi por su dependencia en A, lo que permite Bi finalizar la creación de instancias. Luego, como se ha creado una instancia de Bi, Guice puede instanciar Ai con Bi. Entonces, desde Ai ahora es bueno hacerlo, Guice dice Ap para delegar en Ai.

Si A y B no eran las interfaces (y sólo tenía Ai y Bi) esto no sería posible, porque la creación de Ap que requieren volver a extender Ai, que ya necesita un Bi.

Esto es lo que podría ser similar con código:

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

El proxy que Guice hace que se vería así:

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

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

Y todo sería alámbrico con esta idea básica:

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

y aquí es lo que sería como si sólo tuviera Ai y Bi, sin interfaces A y B.

public class Ap extends Ai { 
    private Ai delegate; 

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

Si Acabo de tirar suficientes clases detrás de las interfaces, será todo bien?

Supongo que hay restricciones estrictas sobre cómo se puede interactuar con el proxy en el constructor. En otras palabras, si B intenta llamar a A antes de que Guice haya tenido la oportunidad de poblar el proxy de A con la A real, entonces esperaría una RuntimeException.

7

Si bien el enfoque de "inyectar una interfaz" es totalmente válido, e incluso podría ser la mejor solución en algunas ocasiones, en general, puede usar una solución más simple: Proveedores.

Para cada clase de guice "A", guice también ofrece un "Provider<A>". Esta es una implementación interna de la interfaz javax.inject.Provider, cuyo mensaje get() será "return injector.getInstance(A.class)". No tiene que implementar la interfaz usted mismo, es parte de la "magia mágica".

De este modo se puede acortar el A-> B, BA ejemplo 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); 
}} 

prefiero esto, debido a que su más fácil de leer (que no se dejan engañar a creer que el constructor ya tiene una instancia de "B ") y dado que podría implementar los proveedores usted mismo, aún así funcionaría" a mano ", fuera del contexto de la guía (para probar, por ejemplo).

+0

También prefiero este enfoque. Significa que no tiene que crear interfaces cuando de otro modo no las necesitaría. También es más rápido/menos potencialmente complicado cambiar la inyección a un proveedor que crear interfaces. – specialtrevor

+0

¿Qué tal tener una dependencia explícita de 'Guice' en tu código de aplicación? Siempre he pensado que es bueno mantenerse independiente del marco DI. Antes de que introdujeses 'Provider' en tu código, podrías haber cambiado para decir' Spring' pero ya no es posible. –

+0

Estoy hablando de javax.inject.Provider , una interfaz estándar JSR330. No se requiere dependencia de guice. –

2

Aquí es @ respuesta de jan-Galinski, rehacerse en 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!") 
} 
Cuestiones relacionadas