0

リフレクションを使用して動作する既存のコードがありますが、可能であれば依存性注入と Guice を使用してオブジェクトの作成を開始したいと考えています。

現在の仕組みは次のとおりです。

  1. 構成 ( .properties) ファイルがロードされ、次のような文字列が含まれます。
    • objects=Foo,^ab..$;Bar,^.bc.$;Baz,i*
    • 注: FooBar、およびBazは実装するクラスです。MyInterface
    • 各ペアには、ペアになっている正規表現があります。
  2. 入力データは別のソースから供給されます。この例を想像してみてください。データは次のとおりです。
    • String[]{ "abab", "abcd", "dbca", "fghi", "jklm" }
  3. 次に、Guice によって作成された の新しいインスタンスFooを作成したいと考えてBarいます。Baz
    • この場合、作成されるインスタンスは次のようになります。
      • new Foo("abab");
      • new Foo("abcd");
      • new Bar("abcd");
      • new Bar("dbca");
      • new Baz("fghi");
      • "jklm"一致するパターンがないため、新しいインスタンスは作成されません。

リフレクションを使用して、現在どのように機能するかを次に示します (これは、sscceごとに実行できる最善の方法です)。

public class MyInterfaceBuilder {
    private Classloader tcl = Thread.currentThread().getContextClassLoader();

    private Pattern p;
    private Class<? extends MyInterface> klass;

    public InterfaceBuilder(String className, String pattern) {
        this.pattern = Pattern.compile(pattern);
        this.klass = makeClass(className);
    }

    private static Class<? extends Interface> makeClass(String className) {
        String fullClassName = classPrefix + className;
        Class<?> myClass;
        try {
            myClass = tcl.loadClass(fullClassName);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Class not found: " + fullClassName, e);
        } 

        if(MyInterface.class.isAssignableFrom(myClass)) {
            return (Class<? extends MyInterface>) myClass; 
        } else {
            throw new IllegalArgumentException(fullClassName + " is not a MyInterface!");
        }
    }

    public MyInterface makeInstance(String type) {
        if (pattern == null || pattern.matcher(type).find()) {
            MyInterface newInstance = null;
            try {
                newInstance = klass.getConstructor(String.class).newInstance(type);
            } catch (Exception e) {
                // Handle exceptions
            }
            return newInstance;
        } else {
            return null;
        }
    }
}

Guice を使用して、この機能 (実行時に動的にクラスをロードし、正確に一致するインスタンスを作成する) を複製するにはどうすればよいですか?

4

3 に答える 3

1

Guiceだけを使用して、反射なしでこれを行うことはできないと確信しています。これは、Guice がそのようなもののために作られていないためです。Guice の仕事は依存関係の管理を支援することであり、オブジェクトを作成するためのさまざまな戦略を支援することではありません (まあ、ある程度はそうですが、必要なほどではありません)。

ただし、ファイルからの情報を使用して作成されたオブジェクトを他のオブジェクトの依存関係として使用する必要がある場合は、それを行うことができます。オブジェクトをある種のマップにプリロードするだけで、次のようなことができると思います。

Map<String, MyInterface> myInterfaceMap;
// You fill it with pairs "abcd" -> new Foo("abcd"), "abab" -> new Foo("abab") etc

その場合、2 つの可能性が存在します。文字列キーのセットが静的に既知であり、それを利用したい場合 (たとえば、いくつかのキーを持つオブジェクトをいくつかのクラスに注入し、他のキーを持つオブジェクトを別のクラスに注入する)、マップをモジュールに渡し、@Namedバインディング アノテーションを使用して、動的にバインディングのセットを作成します。

for (Map.Entry<String, MyInterface> entry : myInterfaceMap) {
    bind(MyInterface.class)
        .annotatedWith(Names.named(entry.getKey()))
        .toInstance(entry.getValue());
}

この後、これらのオブジェクトを次のように注入できます。

class SomeOtherClass {
    // previous 'new Foo("abcd")' object will be injected here
    @Inject
    SomeOtherClass(@Named("abcd") MyInterface interface) {
        // whatever
    }
}

文字列キーのセットが動的である場合、実行時にこれらのオブジェクトをコレクションとして検査する必要があります。この場合、通常どおりバインドできます。

bind(new TypeLiteral<Map<String, MyInterface>>() {}).toInstance(myInterfaceMap);

次に、それを注入できます。

@Inject
SomeOtherClass(Map<String, MyInterface> interface) {
    // whatever
}

明らかに、キーのセットが静的であってもマップをバインドでき、その逆も同様です。つまり@Named、キーのセットが動的であっても、複数のバインドを作成できます。ただし、これらのユースケースはほとんどないと思います。

上記は、オブジェクトを他のオブジェクトに注入したい場合にのみ当てはまることに注意してください。上記の例は、オブジェクト独自の依存関係の注入をサポートするように簡単に変更できます。ただし、どちらにも当てはまらない場合、つまり、オブジェクトを依存関係として注入したくない場合や、オブジェクト自体に依存関係がない場合は、このタスクに Guice はまったく必要ない可能性があります。

UPDATE(コメントを考慮して)

オブジェクトの依存関係を注入します。

キー文字列をコンストラクターを介してオブジェクトに提供する必要がある場合、最も簡単な方法は、メソッド/フィールド インジェクションを使用することだと思います。このようにして、プロセス全体が次のようになります。まず、通常どおりオブジェクトを作成してから、次のようInjector.injectMembers()にメソッドをループで使用します。

Map<String, MyInterface> myInterfaceMap = ...;  
Injector injector = ...;  // create the injector
for (MyInterface myInterface : myInterfaceMap.values()) {
    injector.injectMembers(myInterface);
}

これは考えられる最も単純な解決策ですが、オブジェクトのすべての依存関係がコンストラクターではなくメソッドを介して提供される必要があります。

コンストラクターを介して依存関係を提供する必要がある場合、事態はさらに複雑になります。クラスのファクトリを手動で作成し、それを Guice と統合する必要があります。ファクトリは次のようになります。

public interface MyInterfaceFactory {
    MyInterface create(String name);
}

public class ReflectiveFromFileMyInterfaceFactory implements MyInterfaceFactory {
    // You have to inject providers for all dependencies you classes need
    private final Provider<Dependency1> provider1;
    private final Provider<Dependency2> provider2;
    private final Provider<Dependency3> provider3;

    @Inject
    ReflectiveFromFileMyInterfaceFactory(Provider<Dependency1> provider1,
                                         Provider<Dependency2> provider2,
                                         Provider<Dependency3> provider3) {
        this.provider1 = provider1;
        this.provider2 = provider2;
        this.provider3 = provider3;
    }

    @Override
    public MyInterface create(String name) {
        // Here you query the file and create an instance of your classes
        // reflectively using the information from file and using providers
        // to get required dependencies
        // You can inject the information from file in this factory too, 
        // I have omitted it for simplicity
    }
}

次に、ファクトリをモジュールにバインドします。

bind(MyInterfaceFactory.class).to(ReflectiveFromFileMyInterfaceFactory.class);

あとは普通に注入。

ただし、このアプローチでは、クラスにどの依存関係があるかを事前に知っておく必要があります。

クラスが持つ依存関係が事前にわからない場合は、プライベートモジュールと上記の何かを使用して目的を達成できると思いますが、あなたの場合、これはすぐに扱いにくくなります。ただし、プライベート モジュールを使用する場合は、リフレクションを使用する必要がない可能性があります。

于 2013-06-18T15:59:53.233 に答える
0

さらに考えてみると、ランタイム引数をコンストラクターに渡すことよりも、この回答で言及されている作成および構成の概念を使用することを気にする必要があるかどうか疑問に思い始めています。以下の例にはエラー チェックがありませんが、実際の実装バージョンでは、不正なデータに対して多数NullPointerExceptionの およびがスローされます。IllegalArgumentExceptionしかし、ここにアイデアがあります:

基本的には、次のようになります。

// This could be done a number of different ways
public static void main() {
  Injector inj = Guice.createInjector(new MyOuterModule());
  Injector child = inj.createChildInjector(new MyPluginModule(/* interfaceFileName? */));
  MyApp app = child.getInstance(MyApp.class);
  app.run();
}


public class MyPluginModule extends AbstractModule {
  @Override
  protected void configure() {
    MapBinder<String, MyInterface> mapBinder
          = newMapBinder(binder(), String.class, MyInterface.class);
    // These could probably be read from a file with reflection
    mapBinder.addBinding("Foo").to(Foo.class);
    mapBinder.addBinding("Bar").to(Bar.class);
  }
}

public class InterfaceFactory {
  private Pattern p;
  @Inject private Map<Provider<MyInterface>> providerMap;
  private Provider<MyInterface> selectedProvider;

  public void configure(String type, String pattern) {
    p = Pattern.compile(pattern);
    selectedProvider = providerMap.get(type);
  }

  public MyInterface create(String data) {
    if(pattern.matcher(data).find()) {
      MyInterface intf = selectedProvider.get();
      intf.configure(data);
    }
  }
}

これは、私が今持っているものよりもずっときれいに見えます。

長所:

  1. Guice を使用してオブジェクトを作成する
  2. 反射は最小限に抑えられ、区画化されています
  3. 依存関係の知識は必要ありません

短所:

  1. 構成せずに作成された場合に何をすべきかを知るために、クラスを作成する必要があります
  2. プラグイン バインディングを追加する前に構成ファイルを読み取るか、コードで定義できるようにする必要があります。
于 2013-06-18T17:19:52.343 に答える
0

最初の回答はすでに大きすぎるため、別の回答を追加しています。

マルチバインダとプライベート モジュールを使用して、必要なものを一見達成することができました。

まず第一に、これらは私を助けたリンクです:
https://groups.google.com/forum/#!topic/google-guice/h70a9pwD6_g
https://groups.google.com/forum/#!topic/google -guice/yhEBKIHpNqY
guice のロボット脚の例を Multibinding で一般化する

基本的な考え方は次のとおりです。まず、名前からクラスへのマッピングを作成します。クラス名は構成ファイルの文字列で定義されているため、これはリフレクションを介して手動で行う必要がありますが、Guice ではClassバインドを確立するために (少なくとも) オブジェクトが必要です。

次に、このマッピングを繰り返し処理し、対応ごとに、注釈付きの をname -> classにバインドするプライベート モジュールをインストールします。また、いくつかの一意の注釈でクラスにバインドします。次に、この のバインディングを公開します。これは、外部モジュールの経由でセットに追加されます。StringnameMyInterfaceclassclassMultibinder

このメソッドを使用すると、クラスの依存関係を自動的に解決でき、各オブジェクトの名前を設定する一般的な方法も提供されます。

更新:コードは次のとおりです: https://github.com/dpx-infinity/guice-multibindings-private-modules

于 2013-06-18T19:44:16.210 に答える