google java ioc-container guice typeliteral

java - google - Inyectar implementación genérica utilizando Guice



google guice (4)

Algo relacionado, espero que alguien lo encuentre útil. En algunos casos, especialmente cuando tiene la instancia java.lang.Class del tipo que desea generalizar, es posible forzar una inyección en tiempo de ejecución extendiendo la clase ParameterizedType.

En la solución a continuación, un método de fábrica crea una Colección genérica <? extiende Number> y Map <K, V> dada una instancia del objeto de clase

Ejemplo.java:

@SuppressWarnings("unchecked") public class Example<K extends Number> { Injector injector = ... public Set<K> foo(Class<K> klass) { CompositeType typeLiteral = new CompositeType(Set.class, klass); Set<K> set = (Set<K>) injector.getInstance(Key.get(typeLiteral)); return set; } public <V> Map<K,V> bar(Class<K> keyClass, Class<V> valueClass) { CompositeType typeLiteral = new CompositeType(Map.class, keyClass, valueClass); Map<K,V> collection = (Map<K,V>) injector.getInstance(Key.get(typeLiteral)); return collection; } }

CompositeType.java:

import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; public class CompositeType implements ParameterizedType { private final String typeName; private final Class<?> baseClass; private final Type[] genericClass; public CompositeType(Class<?> baseClass, Class<?>... genericClasses) { this.baseClass = baseClass; this.genericClass = genericClasses; List<String> generics = ((List<Class<?>>)Arrays.asList(genericClasses)) .stream() .map(Class::getName) .collect(Collectors.toList()); String genericTypeString = StringUtils.join(generics, ","); this.typeName = baseClass.getName() + "<" + genericTypeString + ">"; } @Override public String getTypeName() { return typeName; } @Override public Type[] getActualTypeArguments() { return genericClass; } @Override public Type getRawType() { return baseClass; } @Override public Type getOwnerType() { return null; } }

Me gustaría poder inyectar una implementación genérica de una interfaz 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, podría hacer :

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


Los genéricos que no se retenían en el tiempo de ejecución hacían más difícil comprender el concepto al principio. De todos modos, hay razones por las que la new ArrayList<String>().getClass() devuelve Class<?> Y no Class<String> y aunque es seguro convertirla en Class<? extends String> Class<? extends String> debes recordar que los genéricos están ahí solo para verificaciones de tipo en tiempo de compilación (una especie de validación implícita, si quieres).

Por lo tanto, si desea utilizar Guice para inyectar la MyRepository (con cualquier tipo) cada vez que necesite una nueva instancia de Repository (con cualquier tipo), entonces no tiene que pensar en los genéricos en absoluto, sino que está solo. asegure la seguridad del tipo (por eso recibe esas molestas advertencias "sin marcar").

Aquí hay un ejemplo de código que funciona 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); } }

Esto imprime:

I''m a String 33

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

int i = collection.get(0)

Pero si desea que un objeto inyectable ya esté fundido y listo, puede solicitar la List<String> lugar de solo la Lista, pero Guice tratará esa variable Tipo como parte de la clave de enlace (similar a un calificador como @ Llamado). Lo que esto significa es que si desea que la inyección Específicamente la List<String> sea ​​de ArrayList<String> y la List<Integer> sea ​​de LinkedList<Integer> , Guice le permite hacer eso (no probado, conjeturado).

Pero hay una trampa:

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

Como se puede observar , los literales de clase no son genéricos . Ahí es donde usas TypeLiterals de Guice.

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

TypeLiterals conserva la variable de tipo genérico como parte de la metainformación para TypeLiterals a la implementación deseada. Espero que esto ayude.


Para utilizar los genéricos con Guice, debe usar la clase TypeLiteral para enlazar las variantes genéricas. Este es un ejemplo de cómo se ve la configuración del inyector de Guice:

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

(El repositorio es la interfaz genérica, MyRepository es la implementación genérica, Class1 es la clase específica utilizada en los genéricos).


Puede usar (¿abuso?) @ImplementedBy anotación @ImplementedBy para que Guice genere enlaces genéricos para usted:

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

Siempre que los enlaces justo a tiempo estén habilitados, puede inyectar el Repository<Whatever> sin ningún enlace explícito:

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 destino del enlace 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]]}

Por lo general, no es un problema, pero significa que MyRepository no puede inyectar un TypeLiteral<T> para averiguar su propio tipo en tiempo de ejecución, lo que sería particularmente útil en esta situación. Aparte de eso, a mi entender, esto funciona bien.

(Si alguien tiene ganas de arreglar esto, estoy bastante seguro de que solo necesitaría algunos cálculos adicionales por aquí para completar los parámetros de tipo de destino desde la clave de origen).