使用Guice注入通用实现

时间:2010-11-21 16:34:42

标签: java ioc-container guice typeliteral

我希望能够使用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
  }
}

在使用Castle.Windsor的C#中,我能够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;
  }
}

4 个答案:

答案 0 :(得分:61)

为了在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>>(){});
  }
}

(存储库是通用接口,MyRepository是通用实现,Class1是泛型中使用的特定类)。

答案 1 :(得分:5)

在运行时没有保留的泛型确实使得最初掌握这个概念变得更加困难。 无论如何,有new ArrayList<String>().getClass()返回Class<?>而不是Class<String>的原因虽然可以安全地将其转换为Class<? extends String>,但您应该记住,泛型只适用于编译时类型检查(有点像隐式验证,如果你愿意的话)。

因此,如果您想在需要MyRepository(任何类型)的新实例时使用Guice注入Repository(任何类型)实现,那么您不必考虑关于仿制药,但你自己确保类型安全(这就是为什么你得到那些讨厌的&#34;未经检查&#34;警告)。

这是一个代码工作正常的例子:

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 设置为字符串,则会出现异常。

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将泛型类型变量保留为元信息的一部分,以映射到所需的实现。希望这会有所帮助。

答案 2 :(得分: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来填充源密钥中的目标类型参数。)

答案 3 :(得分:0)

有点相关,希望有人会觉得这很有用。在某些情况下,尤其是当您具有要泛化的类型的java.lang.Class实例时,可以通过扩展ParameterizedType类来强制在运行时进行注入。

在下面的解决方案中,工厂方法创建一个通用集合&lt; ? extends Number&gt; Map&lt; K,V&gt; 给定类对象的实例

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