使用 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 类在运行时强制注入。

在下面的解决方案中,工厂方法创建了一个通用 CollectionMap 给定类对象的实例

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&lt;Whatever&gt;,而无需任何显式绑定:

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

关键是绑定的目标是MyRepository,而不是MyRepository&lt;T&gt;

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&lt;T&gt; 来确定自己的类型,这在这种情况下特别有用。除此之外,据我所知,这很好用。

(如果有人想解决这个问题,我很确定它只需要一些额外的计算 around here 来从源键中填写目标类型参数。)

【讨论】:

【参考方案3】:

在运行时未保留的泛型确实使一开始更难掌握这个概念。 无论如何,new ArrayList&lt;String&gt;().getClass() 返回Class&lt;?&gt; 而不是Class&lt;String&gt; 是有原因的,尽管将其转换为Class&lt;? extends String&gt; 是安全的,但您应该记住泛型仅用于编译时类型检查(有点像隐式验证,如果你会的)。

因此,如果您想在需要 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&lt;String&gt; 而不仅仅是 List,但是 Guice 会将该 Type 变量视为绑定键的一部分(类似于一个限定符,例如作为@Named)。这意味着如果您希望注入特别是 List&lt;String&gt;ArrayList&lt;String&gt; 实现和 List&lt;Integer&gt;LinkedList&lt;Integer&gt;,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、MyRepository 等)。这就是温莎的例子。 很抱歉,我应该更仔细地阅读您的问题。我自己一直在研究这种类型的 Guice 泛型用法,但我也无法解决它。我想解决它的一种方法是扩展 Guice 并编写自己的模块(助手)。通过使用 Java 反射 API,您可以找到所有注入变体并绑定它们。

以上是关于使用 Guice 注入通用实现的主要内容,如果未能解决你的问题,请参考以下文章

使用 CDI/Weld 注入通用 Bean

TestNG 中使用 Guice 来进行依赖注入

使用Dagger2做静态注入, 对比Guice.

Guice 注入空指针

JUnit测试中的Guice注入器[关闭]

Guice - 在构造函数中使用注入字段