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 毎ではなくて、インジェクター毎にシングルトン」であるというところが一番はまりそうな挙動な気がした。