2010-11-21 19 views
41

Me gustaría poder aplicar una implementación genérica de una interfaz genérica usando Guice.Inyectar implementación genérica utilizando Guice

public interface Repository<T> { 
    void save(T item); 
    T get(int id); 
} 

public MyRepository<T> implements Repository<T> { 
    @Override 
    public void save(T item) { 
    // do saving 
    return item; 
    } 
    @Override 
    public T get(int id) { 
    // get item and return 
    } 
} 

En C# usando Castle.Windsor, me gustaría ser capaz to do:

Component.For(typeof(Repository<>)).ImplementedBy(typeof(MyRepository<>)) 

pero no creo que exista el equivalente en Guice. Sé que puedo usar TypeLiteral en Guice para registrar implementaciones individuales, pero ¿hay alguna manera de registrarlas todas a la vez como en Windsor?

Editar:

Aquí hay un ejemplo de uso:

Injector injector = Guice.createInjector(new MyModule()); 
Repository<Class1> repo1 = injector.getInstance(new Key<Repository<Class1>>() {}); 
Repository<Class2> repo2 = injector.getInstance(new Key<Repository<Class2>>() {}); 

Aunque el uso más probable sería la inyección en otra clase:

public class ClassThatUsesRepository { 
    private Repository<Class1> repository; 

    @Inject 
    public ClassThatUsesRepository(Repository<Class1> repository) { 
    this.repository = repository; 
    } 
} 
+0

Podría añadir un fragmento que muestra cómo lo haría me gusta _use_ esto? –

+2

Estoy contigo, quiero hacer lo mismo. Todos deberían tener este problema. Debe haber algo que no nos dicen. :) – PapaFreud

+0

Me gustaría saber la solución también, no sé nada de C#, pero obviamente el modo C# es mucho más moderno. – Mike

Respuesta

51

Para utilizar los genéricos con Guice necesita usar la clase TypeLiteral para vincular las variantes genéricas. Este es un ejemplo de cómo eres de configuración del inyector Guice podría parecer:

package your-application.com; 

import com.google.inject.AbstractModule; 
import com.google.inject.TypeLiteral; 

public class MyModule extends AbstractModule { 
    @Override 
    protected void configure() { 
    bind(new TypeLiteral<Repository<Class1>>(){}) 
     .to(new TypeLiteral<MyRepository<Class1>>(){}); 
    } 
} 

(Repositorio es la interfaz genérica, MyRepository es la implementación genérica, Clase 1 es la clase específica utilizada en los genéricos).

+6

Así es como lo estoy haciendo. Lo que esperaba hacer es eliminar la necesidad de registrar cada implementación individual (MyRepository , MyRepository , etc.). Eso es lo que hace el ejemplo de Windsor. –

+2

Disculpa, debería haber leído tu pregunta con más cuidado. He estado investigando ese tipo de uso de genéricos de Guice, pero tampoco pude resolverlo. Supongo que una forma de resolverlo sería ampliar Guice y escribir tu propio Módulo (ayudante). Con el uso de la API de reflexión de Java, puede encontrar todas las variantes de inyección y vincularlas. – Kdeveloper

3

Los genéricos que no se conservan en el tiempo de ejecución seguro hicieron más difícil captar el concepto al principio. De todos modos, hay razones new ArrayList<String>().getClass() devuelve Class<?> y no Class<String> y aunque es seguro convertirlo a Class<? extends String> debe recordar que los genéricos están ahí solo para verificaciones de tipo de tiempo de compilación (algo así como la validación implícita, si lo desea).

Así que si quieres usar Guice para inyectar la implementación MyRepository (con cualquier tipo) cuando necesites una nueva instancia de Repository (con cualquier tipo), entonces no tienes que pensar en genéricos para nada, pero estás por su propia cuenta para garantizar la seguridad del tipo (es por eso que obtienes esas molestas advertencias "sin marcar").

Aquí es un ejemplo de código que funciona muy bien:

public class GuiceTest extends AbstractModule { 

    @Inject 
    List collection; 

    public static void main(String[] args) { 
     GuiceTest app = new GuiceTest(); 
     app.test(); 
    } 

    public void test(){ 
     Injector injector = Guice.createInjector(new GuiceTest()); 
     injector.injectMembers(this); 

     List<String> strCollection = collection; 
     strCollection.add("I'm a String"); 
     System.out.println(collection.get(0)); 

     List<Integer> intCollection = collection; 
     intCollection.add(new Integer(33)); 
     System.out.println(collection.get(1)); 
    } 

    @Override 
    protected void configure() { 
     bind(List.class).to(LinkedList.class); 
    } 
} 

Esta impresora:

I'm a String 
33 

Pero esa lista es implementado por un LinkedList. Aunque en este ejemplo, si intentó asignar un int algo que es String, se obtendría una excepción.

int i = collection.get(0) 

Pero si usted desea conseguir un objeto inyectable ya fundido de tipo y dandy puede solicitar List<String> en lugar de sólo la lista, pero luego Guice tratará a esa variable de tipo como parte de la clave de unión (similar a una calificador como @Named).Lo que esto significa es que si desea que la inyección específicamente List<String> sea de la implementación ArrayList<String> y List<Integer> sea LinkedList<Integer>, Guice le permite hacer eso (no conjeturado, con una idea educada).

Pero hay una trampa:

@Override 
    protected void configure() { 
     bind(List<String>.class).to(LinkedList<String>.class); <-- *Not Happening* 
    } 

Como se puede notar literales de clase no son genéricos. Ahí es donde usas el TypeLiterals de Guice.

@Override 
    protected void configure() { 
     bind(new TypeLiteral<List<String>>(){}).to(new TypeLiteral<LinkedList<String>>(){}); 
    } 

TypeLiterals conservan la variable de tipo genérico como parte de la meta-información para asignar a la aplicación deseada. Espero que esto ayude.

0

Puede utilizar (abuso?) La anotación @ImplementedBy hacer Guice generar enlaces de genéricos para usted:

@ImplementedBy(MyRepository.class) 
interface Repository<T> { ... } 

class MyRepository<T> implements Repository<T> { ... } 

Mientras justo-a-tiempo se habilitan las consolidaciones, puede inyectarse Repository<Whatever> sin ningún tipo de unión explícita :

Injector injector = Guice.createInjector(); 
    System.out.println(injector.getBinding(new Key<Repository<String>>(){})); 
    System.out.println(injector.getBinding(new Key<Repository<Integer>>(){})); 

el problema es que el objetivo de la unión es MyRepository, en lugar de MyRepository<T>:

LinkedKeyBinding{key=Key[type=Repository<java.lang.String>, annotation=[none]], source=interface Repository, scope=Scopes.NO_SCOPE, target=Key[type=MyRepository, annotation=[none]]} 
LinkedKeyBinding{key=Key[type=Repository<java.lang.Integer>, annotation=[none]], source=interface Repository, scope=Scopes.NO_SCOPE, target=Key[type=MyRepository, annotation=[none]]} 

Eso no suele ser un problema, pero significa que MyRepository no puede inyectar un TypeLiteral<T> para determinar su propio tipo en tiempo de ejecución, lo que sería particularmente útil en esta situación. Aparte de eso, que yo sepa, esto funciona bien.

(Si alguien se siente como arreglar esto, estoy bastante seguro de que sólo requieren algunos cálculos adicionales around here para rellenar los parámetros de tipo de destino de la clave de origen.)

Cuestiones relacionadas