使用Guice注入通用实现
我希望能够使用Guice注入generics接口的通用实现。
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中存在相同的东西。 我知道我可以在Guice中使用TypeLiteral
来注册各个实现,但是有没有办法象Windsor一样注册它们?
编辑:
以下是一个使用示例:
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; } }
为了在Guice中使用generics,需要使用TypeLiteral类来绑定generics变体。 这是Guice喷油器configuration如何的例子:
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是generics接口,MyRepository是generics实现,Class1是generics中使用的特定类)。
在运行时不被保留的generics确实使得最初难以理解概念。 无论如何,有原因new ArrayList<String>().getClass()
返回Class<?>
而不是Class<String>
,虽然它安全地将其转换为Class<? extends String>
Class<? extends String>
你应该记住,generics只是为了编译时types检查(有点像隐式validation,如果你愿意的话)。
所以,如果你想要使用Guice来注入MyRepository
(任何types)的实现,只要你需要一个新的Repository
实例(任何types),那么你就不必考虑generics了,但是你自己去确保types安全(这就是为什么你得到那些讨厌的“未检查”警告)。
这里是一个代码工作的例子很好:
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
实现的。 虽然在这个例子中,如果你尝试赋值一个inttypes的东西,你会得到一个exception。
int i = collection.get(0)
但是,如果你想获得一个注入对象已经被input和花花公子,你可以要求List<String>
而不是List,但是Guice将把Typevariables作为绑定键的一部分(类似于一个限定符如@命名)。 这意味着如果你想特别注意List<String>
是ArrayList<String>
实现,并且List<Integer>
是LinkedList<Integer>
,那么Guice允许你这样做(没有经过testing,受过良好教育的猜测)。
但有一个问题:
@Override protected void configure() { bind(List<String>.class).to(LinkedList<String>.class); <-- *Not Happening* }
正如你可能注意到类文字不是通用的 。 那就是你使用Guice的TypeLiterals
的TypeLiterals
。
@Override protected void configure() { bind(new TypeLiteral<List<String>>(){}).to(new TypeLiteral<LinkedList<String>>(){}); }
TypeLiterals
将genericstypesvariables保留为元信息的一部分,以映射到所需的实现。 希望这可以帮助。
您可以使用(滥用?) @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>
来在运行时找出它自己的types,这在这种情况下特别有用。 除此之外,据我所知,这工作正常。
(如果有人想解决这个问题,我敢肯定,只需要在这里附加一些额外的计算来从源代码中填入目标types参数。)