6

私は Guice の 3 つの機能 (注入、マルチバインディング、ジェネリック) を組み合わせようとしています。私は生産プロジェクトのプロトタイプを作成するので、ここにあります:

まず、これはジェネリックの小さな階層です (実稼働環境では、N 個のエンティティの階層があります)。

    public interface Type {
    }
    public class Type1 implements Type{
    }
    public class Type2 implements Type {
    }

次に、Factoryで作成したいクラスToCreate1ToCreate2

基本クラス:

    public abstract class AbstractToCreate<T extends Type> {
        public T type;
        public Integer param;

        public AbstractToCreate(T type, Integer param){
            this.type = type;
            this.param = param;
        }
    }

それは継承者です:

    public class ToCreate1 extends AbstractToCreate<Type1>{
        @Inject
        public ToCreate1(Type1 type, @Assisted Integer param) {
            super(type, param);
        }  
    }

   public class ToCreate2 extends AbstractToCreate<Type2> {
        @Inject
        public ToCreate2(Type2 type, @Assisted Integer param) {
            super(type, param);
        }
    }

次に、Factory自体:

    public interface Factory<T extends Type> {
        AbstractToCreate<T> create(Integer param);
    }

そこで、 Factory<Type1>Factory<Type2>を含むマップを注入して、ToInject1ToInject2をそれぞれ作成します。

そこで、 configure メソッドを使用して Guice のAbstractModuleを作成します。

    protected void configure() {
            install(new FactoryModuleBuilder()
                    .implement(new TypeLiteral<AbstractToCreate<Type1>>(){}, ToCreate1.class)
                    .build(new TypeLiteral<Factory<Type1>>(){}));                     
            install(new FactoryModuleBuilder()
                    .implement(new TypeLiteral<AbstractToCreate<Type2>>(){}, ToCreate2.class)
                    .build(new TypeLiteral<Factory<Type2>>(){}));

            MapBinder<String, Factory> mapBinder = MapBinder.newMapBinder(binder(), String.class, Factory.class);
            mapBinder.addBinding("type1").to(new TypeLiteral<Factory<Type1>>(){});
            mapBinder.addBinding("type2").to(new TypeLiteral<Factory<Type2>>(){});
        }

だから、私はそれを注入し@Inject public Map<String, Factory> map;、すべてがOKです:

    Factory<Type1> factory1 = main.map.get("type1");
    Factory<Type2> factory2 = main.map.get("type2");

    AbstractToCreate<Type1> create1 = factory1.create(1);//create1 is ToCreate1 instance
    AbstractToCreate<Type2> create2 = factory2.create(2);//create2 is ToCreate2 instance

前に述べたように、本番システムにはもっと多くのタイプがあるため、AbstractModuleは扱いにくくなります。コードの重複を避け、configureメソッドを修正しました。

    @Override
    protected void configure() {
        this.<Type1>inst(ToCreate1.class);
        this.<Type2>inst(ToCreate2.class);
    }

    private <V extends Type> void inst(Class<? extends AbstractToCreate<V>> clazz) {
        install(new FactoryModuleBuilder()
                .implement(new TypeLiteral<AbstractToCreate<V>>(){}, clazz)
                .build(new TypeLiteral<Factory<V>>(){}));
    }

そして、それはうまくいきません!ギース 言います:

1) ru.test.genericassistedinject.AbstractToCreate<V> cannot be used as a key; It is not fully specified. 

どうしたの?

4

1 に答える 1

4

ここでの問題は、型の消去です。特に、このコード:

private <V extends Type> void inst(Class<? extends AbstractToCreate<V>> clazz) {
    install(new FactoryModuleBuilder()
            .implement(new TypeLiteral<AbstractToCreate<V>>(){}, clazz)
            .build(new TypeLiteral<Factory<V>>(){}));
}

Vランタイムの決定 (使用するバインディング) を行うために型パラメーターに依存しているため機能しませんが、型パラメーターVにはランタイム表現がないため、その値がランタイムに直接影響することはありません。これについての別の考え方: Java はジェネリックの型パラメーターの値を「読み取る」ことはできません。呼び出し元でインスタンス化されたnew TypeLiteral<Factory<V>>(){}ものに関係なく、常に同じ値です。V

消去関連の問題が発生した場合によくあることですが、必要な型を表すランタイム値を追加するのがコツです。この場合、型パラメーターの値をより大きな型に表現したいので、これは特に注意が必要です。

静的型を表すランタイム値を取得する方法はいくつかあります。TypeTokenは1つであり、Class別のものですが、どちらもパラメーターで型を表現し、プログラムでその値を入力することはできません。幸いなことに、Google Guava には別の表現 が含まれcom.google.common.reflect.TypeTokenています。TypeTokens は、変数を使用して型を表すことができ、具体的な表現を使用してその変数をプログラムで「埋める」ことをサポートします。次に例を示します。

new TypeToken<List<V>>() {}.where(new TypeParameter<V>() {}, Integer.class)

List<Integer>実行時の型を表します。

を使用しTypeTokenて、次のように型を構築できます。

 private <V extends Type> void inst(Class<? extends AbstractToCreate<V>> clazz, Class<V> binding) {
    TypeToken<AbstractToCreate<V>> implementationType = new TypeToken<AbstractToCreate<V>>() {}
        .where(new TypeParameter<V>() {}, binding);
    TypeToken<Factory<V>> factoryType = new TypeToken<Factory<V>>() {}
        .where(new TypeParameter<V>() {}, binding);

    @SuppressWarnings("unchecked")  // The type returned by TypeToken::getType is always the type it represents
    Key<AbstractToCreate<V>> key = (Key<AbstractToCreate<V>>) Key.get(implementationType.getType());
    @SuppressWarnings("unchecked")  // as above
    Key<Factory<V>> factoryKey = (Key<Factory<V>>) Key.get(factoryType.getType());

    install(
        new FactoryModuleBuilder()
            .implement(key, clazz)
            .build(factoryKey));
  }

これで、次のように呼び出すことができますinst:

inst(ToCreate1.class, Type1.class);
inst(ToCreate2.class, Type2.class);

すべてが希望どおりに機能します。

ただし、これは非常に手の込んだものであり、それを理解するには、型のコンパイル時表現と実行時表現の違いを十分に理解している必要があります。私だったら、1 回か 2 回しか使用しないと思われるものであれば、混乱の負担がかなり大きいため、これを行いません。これがライブラリまたは何かの一部であり、すべての呼び出し元の作業を節約できる場合にのみ実行します。

于 2018-05-08T15:37:34.280 に答える