web-dev-qa-db-fra.com

Injecter une implémentation générique à l'aide de Guice

J'aimerais pouvoir injecter une implémentation générique d'une interface générique en utilisant 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 # en utilisant Castle.Windsor, je pourrais faire :

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

mais je ne pense pas que l'équivalent existe à Guice. Je sais que je peux utiliser TypeLiteral dans Guice pour enregistrer des implémentations individuelles, mais existe-t-il un moyen de les enregistrer toutes en même temps, comme à Windsor?

Modifier:

Voici un exemple d'utilisation:

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

Bien que l’utilisation la plus probable soit l’injection dans une autre classe:

public class ClassThatUsesRepository {
  private Repository<Class1> repository;

  @Inject
  public ClassThatUsesRepository(Repository<Class1> repository) {
    this.repository = repository;
  }
}
43
Sean Carpenter

Pour utiliser des génériques avec Guice, vous devez utiliser la classe TypeLiteral afin de lier les variantes génériques. Voici un exemple de votre configuration d’injecteur 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>>(){});
  }
}

(Référentiel est l'interface générique, MyRepository est l'implémentation générique, Class1 est la classe spécifique utilisée dans les génériques).

57
Kdeveloper

Les génériques n'étant pas conservés au moment de l'exécution, il était difficile de saisir le concept au début . De toute façon, il y a des raisons pour lesquelles new ArrayList<String>().getClass() renvoie Class<?> et non Class<String> et bien que son coffre-fort soit converti en Class<? extends String>, vous devez vous rappeler que les génériques sont juste pour les contrôles de type à la compilation (un peu comme la validation implicite, si vous voulez).

Donc, si vous voulez utiliser Guice pour injecter une implémentation de MyRepository (avec n'importe quel type) chaque fois que vous avez besoin d'une nouvelle instance de Repository (avec n'importe quel type), vous n'avez pas du tout à penser aux génériques, mais vous êtes seul pour veillez à la sécurité du type (c’est pourquoi vous obtenez cet avertissement "incohérent" embêtant).

Voici un exemple de code fonctionnant parfaitement:

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

Cela imprime:

I'm a String
33

Mais cette liste est implémentée par un LinkedList. Cependant, dans cet exemple, si vous tentiez d'assigner un objet int à un nom String, vous obtiendrez une exception.

int i = collection.get(0)

Mais si vous voulez obtenir un objet injectable déjà transtypé et dandy, vous pouvez demander List<String> au lieu de List uniquement, mais Guice traitera cette variable Type comme faisant partie de la clé de liaison (similaire à un qualificatif tel que @Named). Cela signifie que si vous voulez que l'injection List<String> soit spécifiquement de ArrayList<String> implémentation et que List<Integer> soit de LinkedList<Integer>, Guice vous permet de le faire (non testé, sans devinette éclairée).

Mais il y a un problème:

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

Comme vous pouvez le constater, les littéraux class ne sont pas génériques. C'est là que vous utilisez la variable TypeLiterals de Guice.

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

TypeLiterals conserve la variable de type générique dans la méta-information pour la mapper à l'implémentation souhaitée. J'espère que cela t'aides.

5
SGal

Quelque peu lié, j'espère que cela sera utile. Dans certains cas, notamment lorsque vous avez l'instance Java.lang.Class du type que vous souhaitez généraliser, il est possible de forcer une injection au moment de l'exécution en développant la classe ParameterizedType. 

Dans la solution ci-dessous, une méthode fabrique un générique Collection <? étend Number> et Map <K, V> étant donné une instance de l'objet de classe

Exemple.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;
  }
}
0
ohad serfaty

Vous pouvez utiliser (abuser) l'annotation @ImplementedBy pour que Guice génère des liaisons génériques à votre place:

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

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

Tant que les liaisons just-in-time sont activées, vous pouvez injecter Repository<Whatever> sans aucune liaison explicite:

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

Le problème est que la cible de la liaison est MyRepository, plutôt que 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]]}

Ce n'est généralement pas un problème, mais cela signifie que MyRepository ne peut pas injecter un TypeLiteral<T> pour déterminer son propre type à l'exécution, ce qui serait particulièrement utile dans cette situation. En dehors de cela, à ma connaissance, cela fonctionne bien.

(Si quelqu'un souhaite résoudre ce problème, je suis presque certain que cela nécessiterait quelques calculs supplémentaires ici, pour renseigner les paramètres de type cible à partir de la clé source.)

0
jedediah