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;
}
}
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はジェネリックで使用される特定のクラスです)
実行時に保持されないジェネリックは、最初に概念を把握することを難しくしました。とにかく、new ArrayList<String>().getClass()
がClass<?>
ではなくClass<String>
を返す理由がありますが、Class<? extends String>
にキャストしても安全ですが、ジェネリックはコンパイル時にのみ存在することに注意してください型チェック(同様の暗黙的な検証のようなもの)。
したがって、MyRepository
の新しいインスタンス(任意のタイプ)が必要なときにGuiceを使用してRepository
(任意のタイプ)の実装を注入する場合、ジェネリックについて考える必要はありません。すべてですが、型の安全性を確保するのはあなた自身です(そのため、これらの厄介な「チェックされていない」警告が表示されます)。
正常に機能するコードの例を次に示します。
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の代わりにList<String>
を要求できますが、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
は、目的の実装にマップするメタ情報の一部としてジェネリック型変数を保持します。お役に立てれば。
多少関連性がありますが、うまくいけば誰かがこれを役に立つと思うでしょう。場合によっては、特に一般化したい型のJava.lang.Classインスタンスがある場合、ParameterizedTypeクラスを拡張することにより、実行時に強制的に注入することができます。
以下のソリューションでは、ファクトリメソッドは、クラスオブジェクトのインスタンスを指定して、汎用Collection <?extends Number>およびMap <K、V>を作成します
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;
}
}
@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<T>
ではなくMyRepository
であることです:
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>
を挿入して実行時に独自の型を判別できないことを意味し、これはこの状況で特に役立ちます。それとは別に、私の知る限り、これはうまく機能します。
(誰かがこれを修正したいと思うなら、ソースキーからターゲットタイプのパラメータを入力するために、追加の計算が必要になると確信しています このあたり )