使用 Guice 注入通用实现
Posted
技术标签:
【中文标题】使用 Guice 注入通用实现【英文标题】:Inject Generic Implementation using Guice 【发布时间】:2011-05-13 10:34:55 【问题描述】:我希望能够使用 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 中不存在等价物。我知道我可以在 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;
【问题讨论】:
您能否添加一个 sn-p 显示您希望如何使用这个? 我和你在一起,我想做同样的事情。每个人都应该有这个问题。一定有什么他们没有告诉我们的。 :) 我也想知道解决方案,我对 C# 一无所知,但显然 C# 方式更现代。 仍然没有可用的解决方案?对所有可能的通用值重复绑定是浪费时间。当然,在某些特殊情况下,您实际上可能想要一个不同的实现,但它不应该是默认的。 【参考方案1】:有点相关,希望有人会觉得这很有用。在某些情况下,尤其是当您拥有要泛化的类型的 java.lang.Class 实例时,可以通过扩展 ParameterizedType 类在运行时强制注入。
在下面的解决方案中,工厂方法创建了一个通用 Collection 和 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;
【讨论】:
【参考方案2】:您可以使用(滥用?)@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>
:
LinkedKeyBindingkey=Key[type=Repository<java.lang.String>, annotation=[none]], source=interface Repository, scope=Scopes.NO_SCOPE, target=Key[type=MyRepository, annotation=[none]]
LinkedKeyBindingkey=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 来从源键中填写目标类型参数。)
【讨论】:
【参考方案3】:在运行时未保留的泛型确实使一开始更难掌握这个概念。
无论如何,new ArrayList<String>().getClass()
返回Class<?>
而不是Class<String>
是有原因的,尽管将其转换为Class<? extends String>
是安全的,但您应该记住泛型仅用于编译时类型检查(有点像隐式验证,如果你会的)。
因此,如果您想在需要 Repository
(任何类型)的新实例时使用 Guice 注入 MyRepository
(任何类型)实现,那么您根本不必考虑泛型,但是你自己来确保类型安全(这就是为什么你会收到那些讨厌的“未经检查”的警告)。
这是一个运行良好的代码示例:
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*
您可能会注意到类文字不是通用的。这就是你使用 Guice 的 TypeLiterals
的地方。
@Override
protected void configure()
bind(new TypeLiteral<List<String>>()).to(new TypeLiteral<LinkedList<String>>());
TypeLiterals
保留泛型类型变量作为元信息的一部分,以映射到所需的实现。希望这会有所帮助。
【讨论】:
【参考方案4】:为了在 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>>());
(Repository是泛型接口,MyRepository是泛型实现,Class1是泛型中使用的具体类)。
【讨论】:
这就是我的做法。我希望做的是消除注册每个单独实现的需要(MyRepository以上是关于使用 Guice 注入通用实现的主要内容,如果未能解决你的问题,请参考以下文章