気になる開発プロダクツ

第1回[後編] Guice 1.0 - GoogleからリリースされたDIフレームワーク

前編では、Guiceの概要と導入、そしてGuiceによるDIの設定からインスタンスの生成と実行までの一連の流れについて解説しました。後編ではDIの設定における応用テクニックについて解説します。

応用編

応用編では、DIの設定について3つの方法を紹介します。そして最後にサーブレット(Webアプリケーション)で利用する方法を紹介します。

DIの設定

方法その1-依存性をアノテーションで指定する

GuiceによるDIの設定は、前編で紹介したもの以外にもいくつかの方法が用意されています。ここでは、注入する具象クラスをアノテーションで指定する方法を紹介します。これにより、ソースコードの記述をよりシンプルにすることができます。

具体的には、インターフェースに対応する注入される具象クラスを、アノテーション@ImplementedByで指定します(リスト5)。これでモジュールの作成が不要になると共に、Injectorを生成するcreateInjector()メソッドの引数も不要になります。

リスト5 インターフェースに対応するされる具象クラス(依存性)をアノテーションで指定
  // インターフェースPlayにPlayImplを対応させる
  // (注入する依存性の設定)
  @ImplementedBy( PlayImpl.class )
  public interface Play  {
    void play();
  }

方法その2-注入の対象に名前をつける

依存性を注入する対象が複数存在するときには、それぞれに名前がつけてあると便利です。名前はアノテーション@Namedでリスト6のようにつけます。ここではプリミティブ型(int)の変数に名前をつけていますが、一般のオブジェクトであっても同じです。

リスト6 注入の対象に@Namedで名前をつけた
  class Exam  {
    @Inject public Exam()  {}
    @Inject @Named("perfect") int perfect;  // 満点
    @Inject @Named("high")    int high;     // 高得点
    @Inject @Named("passing") int passing;  // 合格点
  }

@Namedによって名前をつけられた対象に依存性を注入する場合は、リスト7のようにannotatedWith()とnamed()の両メソッドを使います。named()はcom.google.inject.name.Namesクラスのメソッドですが、このクラスの静的(static)インポートによって、メソッド名だけの記述にしています。

リスト7 名前をつけられた対象に定数を設定
  Injector injector = Guice.createInjector( new AbstractModule() {
    protected void configure()  {
      bind( int.class ).annotatedWith( named( "perfect" ) ).toInstance( 100 );
      bind( int.class ).annotatedWith( named( "high"    ) ).toInstance(  80 );
      bind( int.class ).annotatedWith( named( "passing" ) ).toInstance(  60 );
    }
  });
  Exam exam = injector.getInstance( Exam.class );

方法その3 - Providerによるインスタンスの生成

先ほどまでの例では、new演算子でインスタンスを生成するのとあまり変わりがないと思うかもしれません。しかしGuiceでは、インスタンスの生成時により複雑な処理を行うこともできます。それを担うのがProviderです。

たとえば通販サイトを想定して、注文明細データを持つCartインスタンスを内包したOrderインスタンスを生成するとします。このとき、Injectorを使ってこのインスタンスを生成するにはどうしたらよいでしょうか。ただしOrderとCartは同じインターフェースを持っていないとします。こうしたときProviderを使うのが有効です。

先にInjectorとモジュールを見てみましょう(リスト8)。toProvider()メソッドと、OrderProviderクラスが新たに現れました。このクラスはすぐあとで作成しますが、Orderインスタンスを生成する処理が記述されています。

リスト8 Provider(OrderProvider)が用いられるInjectorの生成
  Injector injector = Guice.createInjector( new AbstractModule() {
    protected void configure()  {
      bind( Order.class ).toProvider( new OrderProvider( 1 ) );
    }
  });

OrderProviderクラスはリスト9のように作成しました。インスタンスの生成はget()メソッドで行われます。このメソッドの戻り値の型とProvider<Order>における<Order>の部分とが同じでなければなりません。この部分は<T>のようにテンプレート型で表すこともできます。その場合はget()メソッドの戻り値もT型として宣言します。

リスト9ではCartのコンストラクタに付与されたIDが引数で渡されますが、このIDはInjectorによりOrderインスタンスが生成されるたびに1ずつ増えるようになっています。つまり注文が入るたびに付与されるIDが増えていくということになります。本来の業務アプリケーションであれば、IDはデータベースなどで生成されたものを使うことになるでしょう。

リスト9 Orderインスタンスを生成するProviderクラス(OrderProvider)
  class OrderProvider implements Provider<Order>  {
    int id = 0;
    public OrderProvider( int id )  {  this.id = id;  }
    public Order get()  {
      // CartインスタンスにIDを付与している
      return  new Order( new Cart( id++ ) );
    }
  }

サーブレット(Webアプリケーション)での利用

執筆時点では、Guiceのドキュメントにあまり詳しくは記述されていませんが、実はサーブレットでGuiceを利用する方法も用意されています。その際、前提としてサーブレットがデプロイされるコンテキストで、サーブレットフィルタGuiceFilterが有効になっていなくてはなりません。

有効にするには、GuiceのJARファイルをコンテキストの/WEB-INF/libフォルダにデプロイした上で、同コンテキストのweb.xmlにリスト10の内容を追加します。追加する位置についてはweb.xmlの仕様を参照してください。

リスト10 GuiceFilterを有効にする設定(web.xmlに追加)
  <filter>
    <filter-name>Guice Servlet Filter</filter-name>
    <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Guice Servlet Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

サーブレットでGuiceを利用すると、以下のようにInjectorからリクエスト(HttpServletRequest)やセッション(HttpSession)のインスタンスが取得できるようになります。

HttpServletRequest request =
       injector.getInstance( HttpServletRequest.class );

または

ServletRequest request =
       injector.getInstance( ServletRequest.class );

このため、サーブレットで利用される業務クラスが、リクエストやセッションを引数として宣言する必要がなくなります。こうするとサーブレットと業務クラスとの依存度を少なくなり、業務クラス単体でのテストがやりやすくなります。

これを実現するには、InjectorでServletModuleを利用します(リスト11)。そしてInjectorを業務クラスで利用できるようにしておきます。他のDI設定が必要であれば、configure()メソッドで一緒に設定しておくとよいでしょう。

リスト11 Injectorの生成
  Injector injector = Guice.createInjector( new AbstractModule() {
    protected void configure() {
      install( new ServletModule() );
      // ..... その他のDI設定 .....
    }
  });

また、以下のように@RequestParametersをMap型の変数に付記すれば、業務クラスにリクエストパラメータを注入することもできますので、業務クラスをPOJO(Plain Old Java Object)として扱いやすくなります。

@Inject @RequestParameters Map<String, String[]> params;

おわりに

Googleのエンジニアにより開発されたDIフレームワークGuiceについて紹介しましたが、いかがでしたでしょうか。スコープやSpring Frameworkとの連携など、取り上げることができなかった機能がまだまだ存在します。ぜひあなたもGuiceを最後の一滴まで飲み干してください。

なお、ここまで部分だけを紹介したサンプルの、全体のソースコードはGuice10Sample.zipです(web.xmlはTomcat6用)。実際に試してみると、Guiceの味がよくわかることでしょう。

おすすめ記事

記事・ニュース一覧