google guiceでインジェクションするクラスを実行時に指定する方法
はじめに
- 当ページは、「google guiceをベースにWebアプリケーションを作ってみよう」の1コンテンツです。
- google guiceを使ったDIのあれこれについてまとめています。
インジェクションするクラスを実行時に指定する方法
基本的なインジェクションの回でも述べましたが、google guiceでのDIは以下のように、
public class TestModule extends AbstractModule { @Override protected void configure() { bind(Service.class).to(ServiceImpl.class).in(Scopes.SINGLETON); bind(Client.class).to(Client.class).in(Scopes.SINGLETON); } }
基本的にインジェクションする実装はモジュール内にハードコーディングされています(上記の例でいうところの「ServiceImpl」)。これはこれで、実装クラス名や実装クラスそのものが変更になった場合とかもリファクタリングとか補完が効いたりとかIDEの恩恵が受けれて便利なのですが*1、次のようなサービスの場合はどうでしょうか。
- 他システム接続モジュール*2を内部的に呼び出すサービス
- 単体テスト環境・結合テスト環境では他システムには接続できないので、何もしないダミーの実装をインジェクションする
- 総合テスト環境および本番では、実際に他システムに接続する本物の実装をインジェクションする
デプロイ環境毎にコードを修正するのはあまりにも微妙です。「必ず万人がそうする」とまではいいませんが、「設定ファイルベースでインジェクションする実装を動的に切り替える」というニーズは、やはりありそうな気がします。
- 仕変以外は実装が変更することが無いようなインジェクションはモジュール内で静的に
- 環境によってしょっちゅう実装が変更するようなインジェクションは設定ファイルベースで動的に
という、ハイブリッドなアプローチが個人的には良いような気がします。google guiceの場合、動的に実行時にインジェクションするクラスを決定するにはどうすればいいのでしょう。とりあえず、
public class DynamicInjectorModule extends AbstractModule { @Override protected void configure() { Class<?> clazz = null; Class<? extends Service> sClass = null; String className = "org.chiba.impl.ServiceImpl"; try { clazz = Class.forName(className); sClass = clazz.asSubclass(Service.class); } catch (Exception e) { throw new IllegalStateException(e); } bind(Service.class).to(sClass).in(Scopes.SINGLETON); } }
と書けばモジュールクラスはサービスの実装(ServiceImpl)に直接依存しなくなりますが、問題は
String className = "org.chiba.impl.ServiceImpl";
ここです。例えばここを、
String className = PropertyUtil.getProperty(Service.class.getName());
とかみたいに書ければ、実装クラスに関する情報をプロパティファイルに外出しして実行時に動的に決定できそうです。そんな訳で、続きは「設定ファイルとの連携」で検討することにします。