Внедрить универсальную реализацию с помощью Guice - PullRequest
47 голосов
/ 21 ноября 2010

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

65 голосов
/ 21 ноября 2010

Чтобы использовать дженерики с 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 - специфический класс, используемый в шаблонах).

5 голосов
/ 29 ноября 2016

Обобщения, не сохраняемые во время выполнения, конечно, затрудняли понимание концепции на первый взгляд.В любом случае, существуют причины, по которым 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*
    }

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

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

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

0 голосов
/ 22 марта 2018

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

В приведенном ниже решении фабричный метод создает универсальный Collection <? расширяет 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;
  }
}
0 голосов
/ 15 января 2017

Вы можете использовать (злоупотреблять) аннотацию @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> для определения своего собственного типа во время выполнения, что было бы особенно полезно в этой ситуации. Кроме того, насколько мне известно, это прекрасно работает.

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

...