2010-11-21 3 views
41

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

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

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

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

Не могли бы вы добавить фрагмент кода, показывающим, как вы бы нравится _use_ это? –

+2

Я с вами, я хочу сделать то же самое. У всех должна быть эта проблема. Должно быть, они не говорят нам. :) – PapaFreud

+0

Я тоже хотел бы знать решение, я ничего не знаю о C#, но очевидно, что C# намного более современен. – Mike

ответ

51

Для того, чтобы использовать дженерики с Вам нужно использовать класс 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>>(){}); 
    } 
} 

(Repository является общим интерфейсом, MyRepository является родовой реализацией, Class1 является конкретным классом, используемым в дженерик).

+6

Вот как я это делаю. Я надеялся сделать это, чтобы исключить необходимость регистрации каждой отдельной реализации (MyRepository , MyRepository и т. Д.). Вот что делает пример Виндзора. –

+2

Простите, я должен был внимательно прочитать ваш вопрос. Я изучал этот тип использования дженериков Guice сам, но я тоже не мог его решить. Я предполагаю, что одним из способов его решения будет расширение Guice и создание собственного модуля (помощника). С помощью API отражения Java вы можете найти все варианты Injection и связать их. – Kdeveloper

3

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

0

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

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

Смежные вопросы