2010-05-18 14 views
11

Tengo una jerarquía de objetos existente donde algunos objetos tienen campos que necesitan ser inyectados. También hay algunos otros objetos que se construyen usando Google Guice y necesitan ser inyectados con referencias a algunos objetos de la jerarquía de objetos previamente descrita. ¿Cómo hago ese tipo de inyección con Guice?Cómo @Inject en la jerarquía de objetos existente usando Guice?

El problema es que los objetos de la jerarquía existente no se construyeron usando Guice, y por lo tanto no están sujetos al proceso de inyección por defecto. Existe, por supuesto, el método injector.injectMembers() que puede inyectarse en una instancia de objeto existente, pero no funciona en las jerarquías de objetos.

Para aquellos que se preguntan por qué no puedo construir la jerarquía de objetos mencionada usando Guice. Esta jerarquía representa los objetos de GUI y está construida por un marco de GUI (Apache Pivot) a partir de una descripción de GUI declarativa (de hecho, este proceso se puede describir como deserialización de objetos). De esta manera, la construcción de la interfaz es bastante simple, y solo quiero inyectar ciertas referencias de servicio en objetos de interfaz y viceversa (para devoluciones de llamadas).

El enfoque que estoy a punto de tomar se describe a continuación.

para inyectar en jerarquía de objetos preexistentes dejar que todos los objetos que estén interesados ​​en la inyección de aplicar determinada interfaz, como:

public interface Injectable { 
    void injectAll(Injector injector); 
} 

Aquellos objetos que luego implementar esta interfaz, así:

public void injectAll(Injector injector) { 
    injector.injectMembers(this); 
    for (Injectable child : children) 
    child.injectAll(injector); 
} 

Entonces Solo llamaría al mainWindow.injectAll(injector) para el objeto raíz en la jerarquía y se inyectarán todos los objetos de interés.

Solución no muy agradable, pero hace el trabajo por un lado. Por otro lado, necesito inyectar objetos de esta jerarquía. Supongo que se puede hacer a través de la implementación de un proveedor personalizado para tales objetos.

¿Existe una solución mejor a mi problema? Tal vez también hay algo mal con mi enfoque?

Respuesta

12

Esta solución funcionará, pero me gustaría proponerle una solución ligeramente diferente.

Específicamente, ya que va a atravesar una estructura de objeto profunda, esto realmente parece un trabajo para el patrón de visitante. Además, lo que estás describiendo parece llamar a un inyector de dos etapas: una etapa de "arranque" que puede inyectar cosas necesarias para la jerarquía creada por pivote (pero no puede inyectar ningún elemento creado por pivote) y una segunda etapa ese es el inyector real usado por su aplicación (que puede inyectar cualquier cosa).

Lo que yo sugeriría es este patrón básico: hacer un visitante que atraviesa la jerarquía y, a medida que avanza, hace la inyección en aquellas cosas que lo necesitan y registra aquellas cosas que necesitan ser inyectadas en otro lugar. Luego, cuando termina de visitar todo, usa Injector.createChildInjector para crear un nuevo Injector que puede inyectar cosas del Injector original y cosas de la jerarquía creada por pivote.

En primer lugar definir un visitante que puede golpear todo en esta jerarquía:

public interface InjectionVisitor { 
    void needsInjection(Object obj); 
    <T> void makeInjectable(Key<T> key, T instance); 
} 

a continuación, definir una interfaz para todos los elementos de pivote creados:

public interface InjectionVisitable { 
    void acceptInjectionVisitor(InjectionVisitor visitor); 
} 

Se podría implementar esta interfaz en su clases creadas por pivote como (suponiendo este código en la clase FooContainer):

public void acceptInjectionVisitor(InjectionVisitor visitor) { 
    visitor.needsInjection(this); 
    visitor.makeInjectable(Key.get(FooContainer.class), this); 
    for (InjectionVisitable child : children) { 
    child.acceptInjectionVisitor(visitor); 
    } 
} 

Tenga en cuenta que las dos primeras afirmaciones son opcionales; es posible que algunos objetos de la jerarquía dinámica no necesiten inyección y también podría ser que algunos de ellos no deseen inyectarse más adelante. También, observe el uso de Key - esto significa que si quieres algo de la clase que sea inyectable con una anotación particular, se puede hacer algo como:

visitor.makeInjectable(Key.get(Foo.class, Names.named(this.getName())), this); 

Ahora bien, ¿Cómo se implementa InjectionVisitor? Así es como:

public class InjectionVisitorImpl implements InjectionVisitor { 
    private static class BindRecord<T> { 
    Key<T> key; 
    T value; 
    } 

    private final List<BindRecord<?>> bindings = new ArrayList<BindRecord<?>>(); 
    private final Injector injector; 

    public InjectionVisitorImpl(Injector injector) { 
    this.injector = injector; 
    } 

    public void needsInjection(Object obj) { 
    injector.injectMemebers(obj); 
    } 

    public <T> void makeInjectable(Key<T> key, T instance) { 
    BindRecord<T> record = new BindRecord<T>(); 
    record.key = key; 
    record.value = instance; 
    bindings.add(record); 
    } 

    public Injector createFullInjector(final Module otherModules...) { 
    return injector.createChildInjector(new AbstractModule() { 
     protected void configure() { 
     for (Module m : otherModules) { install(m); } 
     for (BindRecord<?> record : bindings) { handleBinding(record); } 
     } 
     private <T> handleBinding(BindRecord<T> record) { 
     bind(record.key).toInstance(record.value); 
     } 
    }); 
    } 
} 

A continuación, utilizar esto en su método main como:

PivotHierarchyTopElement top = ...; // whatever you need to do to make that 
Injector firstStageInjector = Guice.createInjector(
    // here put all the modules needed to define bindings for stuff injected into the 
    // pivot hierarchy. However, don't put anything for stuff that needs pivot 
    // created things injected into it. 
); 
InjectionVisitorImpl visitor = new InjectionVisitorImpl(firstStageInjector); 
top.acceptInjectionVisitor(visitor); 
Injector fullInjector = visitor.createFullInjector(
    // here put all your other modules, including stuff that needs pivot-created things 
    // injected into it. 
); 
RealMainClass realMain = fullInjector.getInstance(RealMainClass.class); 
realMain.doWhatever(); 

Tenga en cuenta que la forma createChildInjector obras asegura que si tiene alguna @Singleton cosas con destino en el material inyectado en la jerarquía de pivote , obtendrá las mismas instancias inyectadas por su inyector real: el fullInjector delegará el injertoion en el firstStageInjector siempre que el firstStageInjector sea capaz de manejar la inyección.

Editado para agregar: Una extensión interesante de esto (si desea profundizar en la magia profunda de Guice) es modificar InjectionImpl para que registre el lugar en su código fuente que llamó al makeInjectable. Esto le permite obtener mejores mensajes de error de Guice cuando su código accidentalmente le dice al visitante acerca de dos cosas diferentes vinculadas a la misma clave. Para hacer esto, usted quiere añadir un StackTraceElement a BindRecord, grabar el resultado de new RuntimeException().getStackTrace()[1] dentro del método makeInjectable, y luego cambiar a handleBinding:

private <T> handleBinding(BindRecord<T> record) { 
    binder().withSource(record.stackTraceElem).bind(record.key).toInstance(record.value); 
} 
+0

¡Guau! Esta es una respuesta bastante completa y elaborada. Lo aprecio mucho. Y gracias por compartir la magia de Guice :) Voy a intentar el enfoque sugerido. (La respuesta se aceptará mañana.) – dragonfly

0

Se podría inyectar MembersInjectors para inyectar campos anidados. Por ejemplo, esto inyectará profundamente una instancia de Car existente:

public class Car { 
    Radio radio; 
    List<Seat> seats; 
    Engine engine; 

    public Car(...) {...} 

    @Inject void inject(RadioStation radioStation, 
     MembersInjector<Seat> seatInjector, 
     MembersInjector<Engine> engineInjector) { 
    this.radio.setStation(radioStation); 
    for (Seat seat : seats) { 
     seatInjector.injectMembers(seat); 
    } 
    engineInjector.injectMembers(engine); 
    } 
} 

public class Engine { 
    SparkPlug sparkPlug; 
    Turbo turbo 

    public Engine(...) {...} 

    @Inject void inject(SparkPlug sparkplug, 
     MembersInjector<Turbo> turboInjector) { 
    this.sparkPlug = sparkPlug; 
    turboInjector.injectMembers(turbo); 
    } 
} 
+1

El problema con esta solución es que el uso de esta técnica implica que todos los objetos en jerarquía se construyen usando Guice. Que no es el caso. Además, supongo que usar 'MembersInjector ' aquí es excesivo ya que la instancia de Engine se inyectaría normalmente si la instancia de Car se inyecta utilizando Guice. También pasar 'Injector' (o 'MembersInjector' para ese asunto) a través del árbol de objetos no es un caso de uso normal para él. – dragonfly

Cuestiones relacionadas