Внедрить универсальную реализацию с помощью Guice

Я хотел бы иметь возможность внедрить универсальную реализацию универсального интерфейса, используя 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
  }
}

В C# с использованием Castle.Windsor я мог бы сделать:

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

но я не думаю, что эквивалент существует в Guice. Я знаю, что могу использовать TypeLiteral в Guice регистрировать отдельные реализации, но есть ли способ зарегистрировать их все сразу, как в Виндзоре?

Редактировать:

Вот пример использования:

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

Хотя более вероятное использование будет инъекцией в другой класс:

public class ClassThatUsesRepository {
  private Repository<Class1> repository;

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

4 ответа

Чтобы использовать дженерики с Guice, вам нужно использовать класс TypeLiteral для связывания дженериков. Это пример того, как ваша конфигурация инжектора 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>>(){});
  }
}

(Репозиторий - это универсальный интерфейс, MyRepository - универсальная реализация, Class1 - это конкретный класс, используемый в шаблонах).

Несоблюдение обобщений во время выполнения, несомненно, затруднило понимание концепции на первый взгляд. В любом случае, есть причины new ArrayList<String>().getClass() возвращается Class<?> и не Class<String> и хотя безопасно бросить его Class<? extends String> Вы должны помнить, что дженерики существуют только для проверки типов во время компиляции (вроде как неявная проверка, если хотите).

Так что если вы хотите использовать Guice для инъекций MyRepository (с любым типом) реализации всякий раз, когда вам нужен новый экземпляр Repository (с любым типом), тогда вам вообще не нужно думать о дженериках, но вы сами обеспечите безопасность типов (вот почему вы получаете это надоедливое "непроверенное" предупреждение).

Вот пример кода, работающего просто отлично:

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

Это печатает:

I'm a String
33

Но этот список реализуется LinkedList, Хотя в этом примере, если вы попытаетесь присвоить int что-то, что является String, вы получите исключение.

int i = collection.get(0)

Но если вы хотите получить инъецируемый объект, уже приведенный к типу и денди, вы можете попросить List<String> вместо просто List, но тогда Guice будет обрабатывать эту переменную Type как часть ключа привязки (аналогично определителю, например @Named). Это означает, что если вы хотите инъекцию специально List<String> быть из ArrayList<String> реализация и List<Integer> быть из LinkedList<Integer> Guice позволяет вам сделать это (не проверено, обоснованное предположение).

Но есть одна загвоздка:

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

Как вы могли заметить, литералы класса не являются общими. Вот где вы используете Guice's TypeLiterals,

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

TypeLiterals сохранить переменную универсального типа как часть метаинформации для сопоставления с желаемой реализацией. Надеюсь это поможет.

Вы можете использовать (злоупотреблять?) @ImplementedBy аннотация, чтобы заставить Guice генерировать общие привязки для вас:

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

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

Пока привязки вовремя включены, вы можете внедрить Repository<Whatever> без какой-либо явной привязки:

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

Уловка в том, что цель привязки MyRepository, скорее, чем 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]]}

Обычно это не проблема, но это означает, что MyRepository не может ввести TypeLiteral<T> выяснить свой собственный тип во время выполнения, что было бы особенно полезно в этой ситуации. Кроме того, насколько мне известно, это прекрасно работает.

(Если кому-то захочется это исправить, я уверен, что здесь потребуются дополнительные вычисления, чтобы заполнить параметры целевого типа из исходного ключа.)

В некотором роде, надеюсь, кто-то найдет это полезным. В некоторых случаях, особенно если у вас есть экземпляр java.lang.Class того типа, который вы хотите обобщить, можно принудительно выполнить инъекцию во время выполнения, расширив класс ParameterizedType.

В приведенном ниже решении фабричный метод создает универсальную коллекцию расширяет Number> и Map с учетом экземпляра объекта класса

Example.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;
  }
}
Другие вопросы по тегам