使用Guice注入通用实现

56
我希望能够使用 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#中,我可以做到以下操作:点击这里
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;
  }
}

你能添加一段代码片段展示如何“使用”它吗? - Thorbjørn Ravn Andersen
2
我和你一样,也想做同样的事情。每个人都应该遇到这个问题。他们肯定有什么没有告诉我们的。 :) - PapaFreud
我也想知道解决方案,虽然我对C#一无所知,但显然C#的方式更加现代化。 - Mike
1
仍然没有可用的解决方案吗?为所有可能的通用值重复绑定是浪费时间。当然,在某些特殊情况下,您可能确实希望使用不同的实现,但这不应该是默认情况。 - David Nouls
4个回答

81
为了在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是用于泛型的具体类)。


6
这是我的做法。我希望的是消除每个单独实现(MyRepository<Class1>,MyRepository<Class2>等)需要注册的需求。这就是Windsor示例所做的。 - Sean Carpenter
2
非常抱歉,我应该更仔细地阅读您的问题。我一直在研究Guice泛型用法,但我也无法解决它。我想解决它的一种方法是扩展Guice并编写自己的模块(helper)。使用Java反射API,您可以找到所有注入变量并将它们绑定。 - Kdeveloper

4
泛型在运行时不被保留,这使得一开始理解这个概念变得更加困难。无论如何,new ArrayList<String>().getClass() 返回 Class<?> 而不是 Class<String> 的原因有很多。虽然将其强制转换为 Class<? extends String> 是安全的,但你应该记住,泛型仅用于编译时类型检查(类似于隐式验证)。
因此,如果你想使用 Guice 来注入 MyRepository (任何类型)实现,每当你需要一个新的 Repository(任何类型)实例时,你根本不需要考虑泛型,但你必须自己确保类型安全(这就是为什么你会收到那些讨厌的“unchecked”警告)。
以下是代码示例:
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>>(){});
    }
将通用类型变量作为元信息保留,以映射到所需的实现。希望这可以帮助您理解。

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>:
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>以确定自身类型,这在这种情况下会特别有用。除此之外,据我所知,这个方法可以正常工作。

(如果有人想修复这个问题,我相信只需要在这里进行一些额外的计算,就可以填充源键的目标类型参数。)


0
有点相关,希望有人能找到这个有用。在某些情况下,特别是当你拥有想要泛化的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;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接