2010-11-21 14 views
41

Chciałbym móc wstrzykiwać ogólną implementację ogólnego interfejsu za pomocą Guice.Wstrzykiwanie implementacji ogólnej za pomocą 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 
    } 
} 

W języku C# z wykorzystaniem Castle.Windsor, byłbym w stanie to do:

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

ale nie sądzę odpowiednik istnieje w Guice. Wiem, że mogę użyć TypeLiteral w Guice do zarejestrowania poszczególnych implementacji, ale czy istnieje sposób na zarejestrowanie ich wszystkich naraz, tak jak w Windsor?

Edit:

Oto przykład użycia:

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

Chociaż bardziej prawdopodobne wykorzystanie byłoby wstrzyknięcie do innej klasy:

public class ClassThatUsesRepository { 
    private Repository<Class1> repository; 

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

można dodać fragment pokazujący jak byś lubisz to zrobić? –

+2

Jestem z tobą, chcę zrobić to samo. Każdy powinien mieć ten problem. Musi być coś, o czym nam nie mówią. :) – PapaFreud

+0

Chciałbym również znać rozwiązanie, nie wiem nic o C#, ale oczywiste, że sposób C# jest znacznie bardziej nowoczesny. – Mike

Odpowiedz

51

W celu wykorzystania rodzajowych z Guice, musisz użyć klasy TypeLiteral, aby powiązać standardowe warianty. Jest to przykład tego, jak jesteś wtryskiwacz Guice konfiguracja może wyglądać:

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 jest interfejs rodzajowy, MyRepository to ogólna realizacja, Klasa1 jest specyficzna klasa stosowane w rodzajowych).

+6

Tak to robię. Co mam nadzieję zrobić, to wyeliminować potrzebę rejestracji każdej indywidualnej implementacji (MyRepository , MyRepository , itp.). Tak właśnie działa przykład Windsora. –

+2

Przepraszam, że powinienem przeczytać dokładniej twoje pytanie. Szukałem tego samego rodzaju generatorów Guice, ale nie mogłem go rozwiązać. Myślę, że jednym ze sposobów rozwiązania byłoby rozszerzenie Guice i napisanie własnego modułu (pomocnika). Korzystając z interfejsu Java Reflections można znaleźć wszystkie warianty Injection i powiązać je. – Kdeveloper

3

Generics, które nie zostały zachowane w czasie rzeczywistym, z pewnością utrudniło zrozumienie tej koncepcji na początku. Tak czy inaczej, istnieją powody, dla których new ArrayList<String>().getClass() zwraca Class<?>, a nie Class<String> i chociaż można bezpiecznie go rzucić na Class<? extends String>, należy pamiętać, że generyczne są tam tylko do sprawdzania typu kompilacji (coś w rodzaju jawnej weryfikacji, jeśli wolisz).

Więc jeśli chcesz użyć Guice do wstrzykiwania MyRepository (z dowolnym typem) implementacji, gdy potrzebujesz nowej instancji Repository (z dowolnym typem), to nie musisz w ogóle myśleć o generykach, ale jesteś na własną rękę, aby zapewnić bezpieczeństwo typu (dlatego otrzymujesz brzydkie ostrzeżenie "niezatwierdzone").

Oto przykład kod działa dobrze:

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

Drukuje:

I'm a String 
33 

Ale lista jest realizowany przez LinkedList. Chociaż w tym przykładzie, jeśli próbujesz podpisać coś, co jest String, otrzymasz wyjątek.

int i = collection.get(0) 

Ale jeśli chcesz uzyskać obiekt wstrzykiwania już typu lanego i Dandy można poprosić o List<String> a nie tylko listy, ale potem Guice będzie traktować ten typ zmiennej jako część klucza wiązania (podobnie jak kwalifikator taki jak @Named).Oznacza to, że jeśli chcesz, aby iniekcja konkretnie List<String> była implementacją ArrayList<String> i List<Integer> na LinkedList<Integer>, Guice pozwala ci to zrobić (nie testowane, wykształcone zgadywanie).

Ale jest haczyk:

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

Jak można zauważyć klasy literały nie są uniwersalne. To tutaj używasz Guice's TypeLiterals.

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

TypeLiterals zachować zmienną typu rodzajowego w ramach meta-informacji do mapy do pożądanej realizacji. Mam nadzieję że to pomoże.

0

Można użyć (nadużycie?) Z @ImplementedBy adnotacji aby Guice wygenerowania powiązań generycznych dla Ciebie:

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

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

Dopóki just-in-time Wiązania są włączone, można wstrzyknąć Repository<Whatever> bez wyraźnego wiązania :

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

Zapadka jest celem wiązanie się MyRepository zamiast 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]]} 

Zwykle nie stanowi to problemu, ale oznacza, że ​​MyRepository nie może wstrzyknąć TypeLiteral<T> w celu wykrycia własnego typu w czasie wykonywania, co byłoby szczególnie przydatne w tej sytuacji. Poza tym, zgodnie z moją najlepszą wiedzą, to działa dobrze.

(Jeśli ktoś czuje się jak ustalenie tego, jestem prawie pewien, że to po prostu wymaga pewnych dodatkowych obliczeń around here wypełnić parametrów typu target z klucza źródłowego.)