Revision 1 as of 2009-12-09 07:33:08

Clear message
Locked History Actions

guice/OverrideBinding

バインディングをオーバライドする方法

以下の元ネタは、Overriding Bindings in Guice

ProductionModule

製品で使用するモジュールは以下であるとする。

public class ProductionModule extends AbstractModule {
  protected void configure() {
    bind(LoginService.class).to(RemoteLoginService.class);
    bind(DataStore.class).to(MySqlDatabase.class);
    bind(BillerConnection.class).to(SecureBillerConnection.class);
    bind(LoggingEngine.class).to(DistributedLoggingEngine.class);
  }
}

テスト時には、上のモジュールの一部だけを差し替えてテストを行いたいとする。例えば、LoginServiceはテスト用のクラスFakeLoginServiceにバインドしたい。どうすればよいか?単純に以下のようにしてしまうとエラーになってしまう。

Injector injector = Guice.createInjector(new ProductionModule(), 
  new AbstractModule() {
    protected void configure() {
      bind(LoginService.class).to(FakeLoginService.class);
    }
  }
);

一つのLoginServiceに対して複数のバインドが存在するからである。

モジュールを分割する方法

バインディングごとにモジュールを分割する方法がベストであるとのこと。

public class ProductionLoginModule extends AbstractModule {
  protected void configure() {
    bind(LoginService.class).to(RemoteLoginService.class);
  }
}

これらの複数のモジュールを取捨選択してGuice.createInjectorに指定すればよい。

継承を使う方法

ProductionModuleではLoginServiceのバインドする部分を一つのメソッドに追い出しておく。それを継承したTestModuleでは、そのメソッドをオーバライドする。

public class ProductionModule extends AbstractModule {
  protected void configure() {
    bindLoginService();
    bind(DataStore.class).to(MySqlDatabase.class);
    bind(BillerConnection.class).to(SecureBillerConnection.class);
    bind(LoggingEngine.class).to(DistributedLoggingEngine.class);
  }
  protected void bindLoginService() {
    bind(LoginService.class).to(RemoteLoginService.class);
  }
}

public class TestModule extends ProductionModule {
  @Override protected void bindLoginService() {
    bind(LoginService.class).to(FakeLoginService.class);
  }
}

Moduleをオーバライドする方法

※注意:おそらくOverriding indings in Guiceではベータ版の仕様で説明されているものと思われ、Guice2.0正規版では動作しない。正しくは以下のよう記述になる。

テスト用のモジュールを以下のように作成し、

public class OverridesModule extends AbstractModule {
  @Override protected void configure() {
    bind(LoginService.class).to(FakeLoginService.class);
  }
}

元のModuleを以下のようにオーバライドして新しいModuleを作成し、Injectorを生成する。

Module newModule = Modules
    .override(new ProductionModule())
    .with(new OverridesModule());
Injector injector = Guice.createInjector(newModule);

おまけ~PriviteModuleの使い方

PrivateModuleを使うと、

  • exposeしたバインディングだけが外部から使用でき、
  • それ以外のバインディングは内部からのみ使用できる。

以下では、C.classに対するバインディングを複数作成しているが、CImplOneはAからのみ使用可能で、CImplTwoはBからのみ使用可能になる。

package test;
import com.google.inject.*;
public class Main {
  public interface A {    
  }
  public static class AImpl implements A {    
    @Inject C c;
    public String toString() { return c.getClass().toString(); }
  }
  public interface B {    
  }
  public static class BImpl implements B {  
    @Inject C c;
    public String toString() { return c.getClass().toString(); }
  }
  public interface C {
  }
  public static class CImplOne implements C {    
  }
  public static class CImplTwo implements C{    
  }
  
  public static void main(String[]args) {
    Injector injector = Guice.createInjector(
        new PrivateModule(){
          protected void configure() {
            binder().bind(A.class).to(AImpl.class);
            binder().bind(C.class).to(CImplOne.class);
            expose(A.class);
          }
        },
        new PrivateModule(){
          protected void configure() {
            binder().bind(B.class).to(BImpl.class);
            binder().bind(C.class).to(CImplTwo.class);
            expose(B.class);
          }
        }
    );
    
    A a = injector.getInstance(A.class);
    B b = injector.getInstance(B.class);
    System.out.println(a.toString());
    System.out.println(b.toString());
  }
}

実行結果は

class test.Main$CImplOne
class test.Main$CImplTwo