Внедрение общей реализации с использованием 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
  }
}

В С#, используя Castle.Windsor, я мог бы выполнить:

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

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

Edit:

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

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

Ответ 1

Для использования дженериков с 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 - это определенный класс, используемый в генериках).

Ответ 2

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

Итак, если вы хотите использовать 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)

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

Но есть улов:

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

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

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

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

Ответ 3

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

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