Upload page content

You can upload content for the page named below. If you change the page name, you can also upload content for another page. If the page name is empty, we derive the page name from the file name.

File to load page content from
Page name
Comment

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);

テスト用に一部モジュールを入れ替える

テスト時には常に製品用モジュールのバインディングの一部を差し替えて利用するとした場合、 テストユニットの中でいちいちModules.override()を呼び出すの面倒である。以下のようにしてみたらどうか。

import com.google.inject.*;
import com.google.inject.util.*;


public class TestInjector  {
  public static Injector create(Module...overrideModules) {
    Module[]productionModules = new Module[] {
        new ProductionModule(),
        ....
    };
    return Guice.createInjector(
        Modules.override(productionModules).with(overrideModules));    
  }  
}

これをテストユニットでは以下のように呼び出す。

    Injector injector = TestInjector.create(new AbstractModule() {
      @Override protected void configure() {
        bind(LoginService.class).to(FakeLoginService.class);
      }
    });

さらにもしデフォルトでテスト用のオーバーライドモジュールがある場合は、以下のようにも書ける。 overridesは何段になっても構わないようである。

public class TestInjector  {
  public static Injector create(Module...overrideModules) {
    Module[]productionModules = new Module[] {
        new ProductionModule(),
        ....
    };
    Module[]testModules = new Module[] {
        new TestModule(),
        ....
    };
    return Guice.createInjector(
        Modules.override(
            Modules.override(productionModules).with(testModules)
        ).with(overrideModules)
    );        
  }  
}

このようにしておき、さらに個々のテストユニットで好みのオーバーライドが可能だ。

    Injector injector = TestInjector.create(new AbstractModule() {
      @Override protected void configure() {
        bind(LoginService.class).to(FakeLoginService.class);
      }
    });

おまけ~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

Robot Leg Problemも参照のこと。