Java EE 6: Understanding Contexts and Dependency Injection (CDI), Part 1

Java EE 6には、汎用化されたDepencency Injection(DI)をアプリケーションから利用可能な JSR 299: Contexts and Dependency Injection (CDI)仕様が含まれています。今回はこのCDIをJava EEアプリケーションの中でどのように使用していけばよいかを議論してみたいと思います。

CDI: Contexts and Dependency Injectionとは?

現在広く利用されているJava EE 5仕様でもDI機能は盛り込まれていましたが、非常に制限された形で仕様化されていました。インジェクションするオブジェクトも、インジェクションされるオブジェクトもその種類が制限されており、インジェクションに使用するアノーテーションも、インジェクションするオブジェクトの種類によって異なるアノーテーションクラスを使用する必要がありました(例えば、EJBのインジェクションには@EJBを使用し、データソースのインジェクションには@Resourceを使うなど)。そのため、Java EE 5仕様におけるインジェクションの機能は、汎用のDIと区別するため、一般に「リソース・インジェクション」と呼ばれています。Java EE 5におけるリソースインジェクションの制限事項については、以下のエントリを参照してください。

Java EE 6に含まれるCDI (JSR 299)仕様は、Java EE環境におけるDIコンテナの機能をアノーテーションをベースにしたtype safeなアプローチで標準仕様として規格化したものです。JSR 299はドラフト段階では、Web Beansと呼ばれていたもので、標準化プロセスの初期段階ではその仕様の多くの部分はJBoss Seamをベースにしたものでした。その後、他の代表的なDIコンテナであるGoogle GuiceSpring Frameworkのそれぞれのアイデアを集積し、Java EEの他の仕様(JSFやEJBなど)との関係や整合性について合理的に整理して現在の最終仕様となりました。

CDI (JSR 299)の仕様書は全部で98ページとなっており、一般的なJSRの仕様書としては短いドキュメントですが、その仕様の内容は凝縮されており、かなり込み入ったものとなっています。CDIはその仕様の複雑さゆえ、単純に開発を簡単にするものとは言えないかもしれません。CDIを使いこなせるのはおそらく中上級の開発者であると思います。しかし、CDIはアプリケーションを構成するオブジェクト間の結合を静的にも動的にもコントロールすることができる強力なツールであり、大規模開発におけるアプリケーションの品質を高いレベルで保つためのツールであるとも考えられます。Java EE 6環境でのアプリケーションのアーキテクチャ設計、フレームワーク設計を考えている方は、是非CDIを理解してみてください。きっとプロジェクトに有効な様々なアイデアを思いつくと思います。

先ほど述べましたように、CDI仕様はとても奥が深い仕様になっているため、1回のブログエントリでは表面的にも語り尽くすことができません。そのため、何回かのエントリに分けて、CDIについて解説してみたいと思います。今回は、CDIの基本的な使い方と、インジェクションの柔軟性にスポットライトを当ててみたいと思います。

CDIの基本

CDIでは、インジェクションする側のオブジェクトにもされる側のオブジェクトにもほとんど制約がありません。例えば、ビジネス層のセッション・ビーンにインテグレーション層のDAOをインジェクションすることも、Java EE 5ではできませんでしたが、Java EE 6では問題なくできるようになっています。

import javax.inject.Inject;

// セッション・ビーン(EJB)
@Stateless
public class CustomerManageSessionBean {
    @Inject
    private CustomerDAO customerDAO;  // POJOなDAOクラスがインジェクションされる
        :
}

// DAOクラス(POJO)
public class CustomerDAO {
        :
}

上記の例では、セッション・ビーンCustomerManageSessionBeanが生成されるときに、DAOクラスであるCustomerDAOのデフォルトコンストラクタが呼び出され、CostomerDAOのインスタンスがメンバ変数customerDAOにインジェクションされます。このように、CDIによるインジェクション機能を利用するための最低限の条件は、以下の2つです。

  • オブジェクトをインジェクションしたい場所(インジェクション・ポイント)に@Injectアノーテーションを付与する
  • CDIを有効にするEJBモジュール、またはWebモジュールに標準のデプロイメント記述子beans.xmlを含める
  • CDIを利用するには、@Injectアノーテーションをインジェクション・ポイントに付与するだけでなく、beans.xmlをデプロイするモジュールに含めなければいけない点に注意してください。Java EE 6コンテナはモジュールにbeans.xmlが含まれているかどうかによって、CDIランタイムを起動するかどうかを判断する仕様になっています。

    モジュール内にbeans.xmlを配置する場所は、Webモジュールの場合WEB-INF/beans.xml、EJBモジュールの場合はMETA-INF/beans.xmlです。なお、beans.xmlは特筆すべき定義がなければ中身は空で構いません(XMLプロローグ宣言やルートタグ<beans>も記述する必要はありません)。上記のEJBモジュールの例におけるJARファイルの構成を示すと以下のようになります。

    .
    |-- META-INF/
    |   |-- MANIFEST.MF
    |   `-- beans.xml   ← CDIの有効化に必須(中身は空で構わない)
    `-- com/
        `-- example/
            |-- business/
            |   `-- CustomerManageSessionBean.class
            `-- dao/
                `-- CustomerDAO.class
    

    NetBeansを利用する場合は、最新のNetBeans 6.9(現在、Beta版がリリース)からCDIサポート機能が追加されましたので、Webモジュール、またはEJBモジュールのプロジェクトを作成するときのウィザード画面で「Enable Contexts and Dependency Injection」のチェックボックスをチェックすることで、自動的にbeans.xmlが作成されます。

    nb69_cdi_support1
    nb69_cdi_support2

    なお、CDIはEmbeddable EJB Container上でも有効になりますので、EJBに対するインジェクションの動作であれば、アプリケーションサーバにデプロイしなくても確認することができます。Embeddable EJB Containerについては、以下のエントリを参考にしてください。

    インジェクション・ポイントの自由度

    CDIでは、インジェクション・ポイントの自由度も広がっています。CDIではメンバ変数をインジェクション・ポイントとするフィールド・インジェクションだけでなく、任意のメソッドの引数を経由したパラメータ・インジェクションが可能になっています。

    Java EE 5までのリソース・インジェクションでは、インジェクション・ポイントの形式は、メンバ変数か、ビーン・プロパティ仕様に基づくsetterメソッドでなければなりませんでした。

    // Java EE 5仕様のリソース・インジェクションの例:
    @Stateless
    public class CustomerManageSessionBean {
        @EJB
        private CustomerFinder customerFinder;  // フィールド・インジェクション
    
        @Resource(name="jdbc/__default")        // setterインジェクション
        void setDataSource(DataSource ds) {
            this.ds = ds;
        }
        private DataSoruce ds;
            :
    }
    

    CDIによるインジェクションでは、@Injectアノーテーションを任意のメソッドに付与することができ、必ずしもメソッドの形式はsetterメソッドである必要はありません。また、インジェクションされる引数の数が複数あっても構いません。この場合、そのメソッドの全てのパラメータがインジェクション・ポイントになります。

    @Stateless
    public class CustomerManageSessionBean {
    
        private CustomerFinder customerFinder;
        private CustomerDAO customerDAO;
    
        @Inject // パラメータ・インジェクション
        void init(CustomerFinder ejb, CustomerDAO dao) {
            this.customerFinder = ejb;
            this.customerDAO = dao;
        }
    }
    

    上記の例では、セッション・ビーンCustomerManageSessionBeanが生成されるときに、init()メソッドの引数経由で、CustomerFinderとCustomerDAOのインスタンスがインジェクションされます。CDIではこのようなメソッドを「イニシャライザ・メソッド」と呼びます。上記の例では、単にメンバ変数を初期化するだけなので、わざわざイニシャライザ・メソッドとして記述する意味はほとんどありませんが、オブジェクトの初期化時にインジェクションされるオブジェクトに関連して、何らかのロジックを実行したい場合には、イニシャライザ・メソッドを定義するのが有効と思われます。

    インジェクション・オブジェクトの供給方法の自由度

    これまでの例では、インジェクション・ポイントの型(すなわち、メンバ変数の型やイニシャライザ・メソッドの引数の型)とインジェクション・オブジェクトを供給する側のクラスが完全に一致している場合だけを示していましたが、CDIコンテナはクラスの継承関係やインタフェースの実装関係から連想される実体のクラスを探し出してくれます。

    // セッション・ビーン(EJB)
    @Stateless
    public class CustomerManageSessionBean {
        @Inject
        private CustomerDAO customerDAO;  // CustomerDAOImplのインスタンスがインジェクションされる
            :
    }
    
    // DAOクラス(POJO)
    public class CustomerDAOImpl implements CustomerDAO {
            :
    }
    

    上記はインジェクション・ポイントの型がインタフェースの場合ですが、インジェクションするオブジェクトの親クラスがインジェクション・ポイントの型である場合も同様に機能します。ただし、インジェクション・ポイントの型から1つだけ実体のクラスが導出される場合だけ機能する点に注意してください。インジェクション・オブジェクトとして複数のクラスが候補として連想される場合は、他のメタデータを使用して、候補が1つだけになるようにしてあげる必要があります。インジェクション・オブジェクトの候補を絞り込む方法については、別途解説したいと思います。

    また、別の観点ではこれまでの例では、インジェクションされるオブジェクトの生成には、(EJBの例を除いて)該当クラスのデフォルトコンストラクタが使用されていました。しかし、インジェクション・オブジェクトを供給する手段としては必ずしもデフォルトコンストラクタでなければならない訳ではありません。引数付きのコンストラクタや、ファクトリ・パターンにおけるファクトリ・メソッドをインジェクション・オブジェクトの供給方法として定義することができます。

    1) ビーン・コンストラクタの選択

    引数のあるコンストラクタをオブジェクト生成に使用したい場合は、以下のように目的のコンストラクタに@Injectアノーテーションを付与します。

    // セッション・ビーン(EJB)
    @Stateless
    public class CustomerManageSessionBean {
        @Inject
        private CustomerDAO customerDAO; // ← コンストラクタCustomerDAO(DAOUtils)の結果が入る
            :
    }
    
    // DAOクラス(POJO)
    public class CustomerDAO {
        private DAOUtils util;
    
        // この場合、デフォルト・コンストラクタはCDIからは利用されない
        public CustomerDAO() {}
    
        @Inject // CDIビーン・コンストラクタとしてマーク
        public CustomerDAO(DAOUtils util) {
            this.util = util;
        }
            :
    }
    

    上記の例では、メンバ変数customerDAOには、デフォルトコンストラクタの代わりにコンストラクタCustomerDAO#CustomerDAO(DAOUtils)によって生成されたインスタンスが設定されます。なお、このコンストラクタを呼び出すためには引数DAOUtilsを解決しなければなりません。CDIコンテナは再帰的にDAOUtilsオブジェクトの生成方法を解決し、最終的なオブジェクトツリーを生成します。

    2) プロデューサ(@Produces)による依存関係の解決

    もし、インジェクション・オブジェクトの供給をファクトリ・メソッド経由で行いたい場合は、以下のようにファクトリメソッドに@Producesアノーテーションを付与します。

    // セッション・ビーン(EJB)
    @Stateless
    public class CustomerManageSessionBean {
        @Inject
        private CustomerDAO customerDAO; // ← DAOFactory#getCustomerDAO()の結果が入る
            :
    }
    
    import javax.inject.Produces;
    
    // ファクトリ・クラス
    public class DAOFactory {
        @Produces // プロデューサ・メソッドとしてマーク
        public static CustomerDAO getCustomerDAO() {
            return new CustomerDAO(new DAOUtils());
        }
    }
    
    // DAOクラス(POJO)
    public class CustomerDAO {
        private DAOUtils util;
    
        CustomerDAO(DAOUtils util) {
            this.util = util;
        }
            :
    }
    

    このように、メソッドの戻り値により依存関係にあるオブジェクトを供給するメソッドを「プロデューサ・メソッド」と呼びます。プロデューサ・メソッドは、イニシャライザ・メソッドやビーン・コンストラクタと同様に引数を持つことができます。この場合、プロデューサ・メソッドのそれぞれの引数はCDIランタイムによって一意に解決できる必要があります。@Producesアノーテーションはメンバ変数に付与することもできます。この場合、依存関係のあるオブジェクトはそのメンバ変数経由で依存先に供給することになります。このように@Producesアノーテーションがついたメンバ変数を「プロデューサ・フィールド」と呼びます。

    以上、述べてきたようにCDIではインジェクション・ポイントの定義が柔軟で、依存関係のあるオブジェクトの供給方法にも柔軟性があることが理解して頂けたと思います。次回のエントリでは、依存関係のオブジェクトの候補が複数存在する場合に適切なオブジェクトを一意に決定するためのクオリファイヤ(@Qualifier)について解説したいと思います。

    投稿されたコメント:

    コメント
    • HTML文法 不許可
    About

    Takashi Nishigaya
    Principal Consultant
    Technology Solution Consulting
    Oracle Consulting Services

    Search

    Categories
    Archives
    « 7月 2014
      
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
      
           
    今日