Google Guice の散文的な検証

Guice で、@ImplementedBy を使用してインジェクションしているときの
簡単な動作検証。

(親)Main → Service → Logic → Dao

という親子関係で構築されているクラス群を作成した。以下ソースを子から順に。

まず、Dao。実装は、DaoImpl と DaoImpl2。

package test.dao;

import com.google.inject.ImplementedBy;

@ImplementedBy(DaoImpl.class)
public interface Dao {
	public String getName(int key);
}
package test.dao;

public class DaoImpl implements Dao {

	private DaoImpl() {
		System.out.println(this);
	}
	@Override
	public String getName(int key) {
		System.out.print("-->DaoImpl");
		return Integer.toString(key);
	}

}
package test.dao;

public class DaoImpl2 implements Dao {

	private DaoImpl2() {
		System.out.println(this);
	}
	@Override
	public String getName(int key) {
		System.out.print("-->DaoImpl2");
		return Integer.toString(key);
	}

}

次に、Logic。これも実装は2つ。

package test.logic;

import com.google.inject.ImplementedBy;

@ImplementedBy(LogicImpl.class)
public interface Logic {
	public String execute(String arg);
}
package test.logic;

import test.dao.Dao;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class LogicImpl implements Logic {
	@Inject
	private Dao dao;
	
	private LogicImpl() {
		System.out.println(this);
	}
	
	@Override
	public String execute(String arg) {
		System.out.print("-->LogicImpl");
		return arg + dao.getName(1234);
	}

}
package test.logic;

import test.dao.Dao;

import com.google.inject.Inject;

public class LogicImpl2 implements Logic {
	@Inject
	private Dao dao;
	
	private LogicImpl2() {
		System.out.println(this);
	}

	@Override
	public String execute(String arg) {
		System.out.print("-->LogicImpl2");
		return arg + dao.getName(9999);
	}

}

Service。これは実装は一つだけ。

package test.service;

import com.google.inject.ImplementedBy;

@ImplementedBy(ServiceImpl.class)
public interface Service {
	void service();
}
package test.service;
import test.logic.Logic;

import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
class ServiceImpl implements Service {
	
	@Inject
	private Logic logic;

	private ServiceImpl() {
		System.out.println(this);
	}
	
	public void service() {
		System.out.print("ServiceImpl");
		logic.execute(logic.getClass().getName());
		System.out.println("...");
	}
}

そして、上記のブートストラップとなるメインクラス。

package test.main;

import test.dao.Dao;
import test.dao.DaoImpl2;
import test.logic.Logic;
import test.logic.LogicImpl2;
import test.service.Service;

import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Stage;

public class myExample {
	private static void p(String s) {
		System.out.println(s);
	}
	
	public static void main(String[] args) {
		p("インジェクターを生成");
		Injector injector = Guice.createInjector(Stage.PRODUCTION);
		p("インスタンスを取得");
		Service s = injector.getInstance(Service.class);
		p("インスタンスのメソッドを実行");
		s.service();
		
		p("再度インジェクターを生成");
		injector = Guice.createInjector(Stage.PRODUCTION);
		p("インスタンスを取得");
		s = injector.getInstance(Service.class);
		p("再取得");
		s = injector.getInstance(Service.class);
		p("インスタンスのメソッドを実行");
		s.service();
		
		p("Logic と Dao のバインド実装を上書き");
		Injector injector2 = Guice.createInjector(Stage.PRODUCTION,new Module(){
			@Override public void configure(Binder binder) {
				System.out.println(this);
				System.out.println("Logic は Impl2 に");
				binder.bind(Logic.class).to(LogicImpl2.class);
				System.out.println("Dao も Impl2 に");
				binder.bind(Dao.class).to(DaoImpl2.class);
			}});
		p("インスタンスを取得");
		s = injector2.getInstance(Service.class);
		p("インスタンスのメソッドを実行");
		s.service();
		p("上書き前のインジェクターを使用してインスタンスを取得");
		s = injector.getInstance(Service.class);
		p("インスタンスのメソッドを実行");
		s.service();
	}
}

実行時ログは以下。

インジェクターを生成
インスタンスを取得
test.service.ServiceImpl@860d49
test.logic.LogicImpl@1edc073
test.dao.DaoImpl@121f1d
インスタンスのメソッドを実行
ServiceImpl-->LogicImpl-->DaoImpl...
再度インジェクターを生成
インスタンスを取得
test.service.ServiceImpl@18088c0
test.logic.LogicImpl@fec107
test.dao.DaoImpl@1617189
再取得
インスタンスのメソッドを実行
ServiceImpl-->LogicImpl-->DaoImpl...
Logic と Dao のバインド実装を上書き
test.main.myExample$1@872380
Logic は Impl2 に
Dao も Impl2 に
インスタンスを取得
test.service.ServiceImpl@7b6889
test.logic.LogicImpl2@20be79
test.dao.DaoImpl2@1ee4648
インスタンスのメソッドを実行
ServiceImpl-->LogicImpl2-->DaoImpl2...
上書き前のインジェクターを使用してインスタンスを取得
インスタンスのメソッドを実行
ServiceImpl-->LogicImpl-->DaoImpl...

■ 要点
・Serviceクラスはシングルトン
・Logicクラスは Impl だけシングルトン
・@ImplementedBy で指定されたのとは異なる実装を指定して injector を生成しなおしている。

■ わかること
・インジェクションが実際に実行されるのは、injector.getInstance() 時。
・親クラスがシングルトンであれば、子クラスにシングルトン指定が無くても親クラスのインスタンス再取得時に子クラスのインスタンスが再設定されたりはしない。(当たり前か)逆に、親クラスがシングルトンではなく、子クラスがシングルトンの場合は、親クラスのインスタンスを再取得すると、親クラスは別インスタンスとなるが、子クラスは期待通りシングルトンとなる。
・@Singleton 指定は VM 毎ではなくて、インジェクター毎にシングルトン。つまり、インジェクターが別であれば(まったく同じModuleから生成したインジェクターであったとしても)同じインスタンスが生成される。@Singleton 指定をしてもインスタンスを生成する都度インジェクターを生成してたら意味がないということになる。
・同じインターフェイスに対して異なる実装クラスをバインドするModuleで、インジェクターを生成した場合、期待通り、@ImplementedBy での指定を無視して上書きしてくれる。@ImplementedBy が上書き出来るのは単体テスト時に便利というか、できないとちょっとイヤだ。

・LogicImpl をバインドするインジェクターと LogicImpl2 をバインドするインジェクターは同時に使用することができ、期待どおり、同じように Service のインスタンスを取得しても、それぞれ Impl と、Impl2 がインジェクトされた Service のインスタンスを取得できる。(まあ狙ってこんなことしない気がするが、バグでこうちゃってるっていうケースならありそうだ)


「@Singleton 指定は VM 毎ではなくて、インジェクター毎にシングルトン」であるというところが一番はまりそうな挙動な気がした。