web-dev-qa-db-ja.com

Guice:モジュールを注入することは可能ですか?

Depedencyを必要とするモジュールがあります。モジュール自体を注入する方法はありますか?これは少し鶏が先か卵が先かという状況だと思います...

例:

public class MyModule implements Module {

    private final Dependency d_;

    @Inject public MyModule(Dependency d) {
        d_ = d;
    }

    public void configure(Binder b) { }

    @Provides Something provideSomething() {
        // this requires d_
    }
}

この場合の解決策は、@Providesメソッドを本格的なProvider<Something>クラスに変えることだと思います。これは明らかに単純化された例です。私が扱っているコードにはそのような@Providesメソッドがたくさんあるので、それぞれを個別のProvider<...>クラスに分割し、それらを構成するモジュールを導入すると、かなりの混乱が生じます。ボイラープレートの乱雑?

おそらくそれは、ギスに対する私の相対的な無愛想さを反映しているのかもしれませんが、私が上記のことをしたいと思ったかなりの数のケースに出くわしました。私は何かが欠けているに違いない...

25
sxc731

@Providesメソッドは、@Injectアノテーション付きコンストラクターまたはメソッドのパラメーターと同様に、依存関係をパラメーターとして受け取ることができます。

@Provides Something provideSomething(Dependency d) {
   return new Something(d); // or whatever
}

これは文書化されています ここ 、おそらくもっと目立たせることができますが。

29
ColinD

オブジェクトを手動で作成するために依存関係が必要な場合は、プロバイダーまたは@Providesメソッドを使用すると便利です。ただし、バインディング自体を構成する方法を決定するのに役立つ何かが必要な場合はどうなりますか? Guiceを使用してモジュールを作成(および構成)できることがわかりました。

これが(考案された)例です。まず、構成するモジュール:

/**
 * Creates a binding for a Set<String> which represents the food in a pantry.
 */
public class PantryModule extends AbstractModule {
  private final boolean addCheese;

  @Inject
  public ConditionalModule(@Named("addCheese") boolean addCheese) {
    this.addCheese = addCheese;
  }

  @Override
  protected void configure() {
    Multibinder<String> pantryBinder = Multibinder
      .newSetBinder(binder(), String.class);

    pantryBinder.addBinding().toInstance("milk");

    if (addCheese) {
      pantryBinder.addBinding().toInstance("cheese");
    }

    pantryBinder.addBinding().toInstance("bread");
  }
}

PantryModuleは、パントリーにチーズを含めるかどうかを決定するためにブール値が注入されることを期待しています。

次に、Guiceを使用してモジュールを構成します。

// Here we use an anonymous class as the "configuring" module. In real life, you would 
// probably use a standalone module.
Injector injector = Guice.createInjector(new AbstractModule() {
  @Override
  protected void configure() {
    // No cheese please!
    bindConstant().annotatedWith(Names.named("addCheese")).to(false);
    bind(PantryModule.class);
  }
});

Module configuredConditionalModule = injector.getInstance(PantryModule.class);

モジュールが構成されたので、それを使用するようにインジェクターを更新します...

//...continued from last snippet...
injector = injector.createChildInjector(configuredConditionalModule);

そして最後に、パントリーを表す文字列のセットを取得します。

//...continued from last snippet...
Set<String> pantry = injector.getInstance(new Key<Set<String>>() {});

for (String food : pantry) {
  System.out.println(food);
}

すべてのピースをmainメソッドにまとめて実行すると、次の出力が得られます。

milk
bread

「addCheese」ブール値へのバインディングをtrueに変更すると、次のようになります。

milk
cheese
bread

この手法は優れていますが、おそらくInjectorインスタンスを制御できる場合、およびモジュールが複雑な依存関係を必要とする場合にのみ役立ちます。それにもかかわらず、私は実際のプロジェクトでこれが本当に必要であることに気づきました。もし私がそうしたら、他の誰かもそうかもしれません。

12
Jim Hurne

質問はすでに十分に回答されていますが、コリンの例にバリエーションを追加したかっただけです。

class MyModule extends AbstractModule { 
  public void configure() {
    bind(Something.class).toProvider(new Provider<Something>() {
       @Inject Dependency d;
       Something get() { return d.buildSomething(); }
    }
  }
}

@Providesメソッドのアプローチは、この単純なケースについて上記で説明したものよりも明確ですが、実際のプロバイダーをインスタンス化すると、状況によっては役立つ場合もあることがわかりました。メーリングリストから盗んだもの。私自身では起こらなかっただろう;)

4
CJC

new MyModule(d)を呼び出すか、Providerが挿入されたInjector <Something>を作成するだけで、モジュールを初期化する場合の問題は何ですか?これらは、この種の問題を処理する標準的な方法のように思われます。すでに述べたように、引数を指定して_@Provides_メソッドを使用することもできます。

依存関係がオプションの場合は、モジュールを作成し、必要に応じてセッターを呼び出して値を初期化できます(たとえば、new JpaPersistModule(String)を使用して正しい構成をロードしながら、_com.google.inject.persist.jpa.JpaPersistModule_はプロパティでこれを行います) 。

そうでなければ、そうすることが可能かもしれないと思います(そして、createChildInjector(Modules... modules)を呼び出します)が、ほとんどの場合、他のアプローチの1つを好みます。

0