corner imagecorner image
IDEPlatformPluginsDocs & SupportCommunityPartners

@Alternative Bean およびライフサイクル注釈の適用

執筆: Andy Gibson

このページの内容は NetBeans IDE 6.9、7.0、および 7.1 が対象です

JSR-299 で指定されている CDI (Contexts and Dependency Injection) は Java EE 6 の不可欠な部分であり、サーブレット、エンタープライズ Bean、および JavaBeans が、アプリケーションのライフサイクル内で明確なスコープを持って存在できるようにするためのアーキテクチャーを提供します。また、CDI サービスによって、EJB セッション Bean や JSF (JavaServer Faces) 管理対象 Bean などの Java EE コンポーネントが注入可能になり、イベントの起動や監視による疎結合方式の対話が可能になります。

このチュートリアルは、Andy Gibson 氏によって投稿された「CDI 入門パート 2 – 注入」というタイトルのブログをベースにしています。ここでは、@Alternative 注釈を利用して、異なる配備環境向けにアプリケーションを構成する方法や、@PostConstruct@PreDestroy などの管理対象 Bean ライフサイクル注釈を使用して、CDI 注入を Java EE 6 管理対象 Bean の仕様で提供されている機能と組み合わせる方法を示します。

NetBeans IDE は、Contexts and Dependency Injection を組み込みでサポートしています (これには、プロジェクト作成時に beans.xml CDI 構成ファイルを生成するオプション、注釈のためのエディタおよびナビゲーションサポート、一般的に使用される CDI アーティファクトを作成するための各種ウィザードなどが含まれています)。


このチュートリアルを完了するには、次のソフトウェアとリソースが必要です。

ソフトウェアまたはリソース 必須バージョン
NetBeans IDE 6.9、7.0、7.1、Java EE バージョン
Java Development Kit (JDK) version 6
GlassFish サーバー Open Source Edition 3.1 または 3.0.1
cdiDemo2.zip n/a

注:

  • NetBeans IDE の Java バンドル版には、Java EE 6 準拠のコンテナである GlassFish Server Open Source Edition 3.x も含まれています。
  • CDI のサポートは、パッチ 1 を適用すれば NetBeans 6.8 でも使用できます。
  • このチュートリアルのサンプルソリューションプロジェクトをダウンロードできます: cdiDemo3.zip

複数配備の処理

CDI では、注入ポイントに一致する Bean が複数存在する場合でも、あいまい性のエラーを発生させずにパッケージ化できる @Alternative 注釈を使用できます。つまり、2 つ以上の Bean に @Alternative 注釈を適用してから、配備環境に基づいて、使用する Bean を CDI の beans.xml 構成ファイルに指定できます。

これを示すために、次のようなシナリオを考えます。主 ItemProcessor クラスに ItemValidator を注入します。ItemValidator は、DefaultItemValidatorRelaxedItemValidator の両方によって実装されます。ここでの配備要件に基づき、ほとんどの場合に DefaultItemValidator を使用しますが、特定の配備環境向けに RelaxedItemValidator も必要とします。これを解決するために、両方の Bean に注釈を付けてから、アプリケーションの beans.xml ファイルにエントリを追加して、ある配備環境にどの Bean を使用するかを指定します。

この課題で作成されるオブジェクトを示す CDI 図
  1. まず、cdiDemo2.zip ファイル (上記の必要なリソースの一覧表を参照) からサンプルのスタートプロジェクトを抽出します。「ファイル」>「プロジェクトを開く」(Ctrl-Shift-O、Mac の場合は ⌘-Shift-O) を選択してから、コンピュータ上のこのプロジェクトの場所を選択することで、IDE でプロジェクトを開きます。
  2. ItemValidator インタフェースを作成します。

    「新規ファイル」(「新規ファイル」ボタン) ボタンをクリックするか、Ctrl-N (Mac の場合は ⌘-N) を押してファイルウィザードを開きます。
  3. 「Java」カテゴリから「Java インタフェース」を選択します。「次へ」をクリックします。
  4. クラス名として「ItemValidator」、パッケージとして「exercise3」と入力します。
  5. 「完了」をクリックします。新しいインタフェースが生成され、エディタで開かれます。
  6. Item オブジェクトを取って boolean 値を返す isValid() という名前のメソッドを追加します。
    public interface ItemValidator {
        boolean isValid(Item item);
    }
    (エディタのヒントを使用して exercise2.Item のインポート文を追加します。)
  7. ItemProcessor クラスを拡張して新しい機能を組み込みます。エディタで ItemProcessor を開いて、次のように変更します。
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        @Inject @Demo
        private ItemDao itemDao;
    
        @Inject
        private ItemValidator itemValidator;
    
        public void execute() {
          List<Item>  items = itemDao.fetchItems();
          for (Item item : items) {
              System.out.println("Item = " + item + " valid = " + itemValidator.isValid(item));
          }
        }
    }

    エディタのヒントを使用して exercise3.ItemValidator のインポート文を追加します。

  8. 値の制限値をテストするだけの、DefaultItemValidator という名前の ItemValidator の実装を作成します。

    「プロジェクト」ウィンドウで「exercise3」パッケージを右クリックし、「新規」>「Java クラス」を選択します。クラスに「DefaultItemValidator」という名前を付け、「完了」をクリックします。

  9. 次のようにして、DefaultItemValidatorItemValidator を実装し、isValid() メソッドをオーバーライドします。
    public class DefaultItemValidator implements ItemValidator {
    
        @Override
        public boolean isValid(Item item) {
            return item.getValue() < item.getLimit();
        }
    }

    (エディタのヒントを使用して exercise2.Item のインポート文を追加します。)

  10. IDE のメインツールバーにある「プロジェクトを実行」(「プロジェクトを実行」ボタン) ボタンをクリックします。プロジェクトがコンパイルされて GlassFish に配備され、アプリケーションの開始ページ (process.xhtml) がブラウザで開きます。
  11. ページに表示されている「Execute」ボタンをクリックします。IDE に戻って GlassFish のサーバーログを調べます。サーバーログは、「出力」ウィンドウ (Ctrl-4、Mac の場合は ⌘-4) の「GlassFish」タブの下に表示されます。項目が検証されていることが表示されます。制限値より小さい、有効な項目だけが一覧表示されます。
    INFO: Item =  [Value=34, Limit=7] valid = false
    INFO: Item =  [Value=4, Limit=37] valid = true
    INFO: Item =  [Value=24, Limit=19] valid = false
    INFO: Item =  [Value=89, Limit=32] valid = false
    「出力」ウィンドウ - GlassFish のサーバーログ
  12. ここで、条件を緩和して、値が制限の 2 倍を超える場合にだけ項目を無効と見なす別のサイトへ配備するシナリオを考えます。このロジックのために、ItemValidator インタフェースを実装する別の Bean を用意します。

    RelaxedItemValidator という名前の ItemValidator の新しい実装を作成します。「プロジェクト」ウィンドウで「exercise3」パッケージを右クリックし、「新規」>「Java クラス」を選択します。クラスに「RelaxedItemValidator」という名前を付け、「完了」をクリックします。

  13. 次のようにして、RelaxedItemValidatorItemValidator を実装し、isValid() メソッドをオーバーライドします。
    public class RelaxedItemValidator implements ItemValidator {
    
        @Override
        public boolean isValid(Item item) {
            return item.getValue() < (item.getLimit() * 2);
        }
    }

    (エディタのヒントを使用して exercise2.Item のインポート文を追加します。)

  14. 「プロジェクトを実行」 (「プロジェクトを実行」ボタン) ボタンをクリックして、プロジェクトを実行します。今度はプロジェクトの配備に失敗します。
  15. 出力ウィンドウ (Ctrl-4、Mac の場合は ⌘-4) でサーバーログを調べます。「あいまいな依存関係」の問題を報告するエラーメッセージが確認できます。これは、現時点で同じインタフェースを実装しているクラスが 2 つあるために起こります。
    org.glassfish.deployment.common.DeploymentException: Injection point has ambiguous dependencies.
    Injection point: field exercise2.ItemProcessor.itemValidator;
    Qualifiers: [@javax.enterprise.inject.Default()];
    Possible dependencies: [exercise3.RelaxedItemValidator, exercise3.DefaultItemValidator]

    CDI の実装である Weld は、特定の注入ポイントに RelaxedItemValidatorDefaultItemValidator のどちらを使用するかを決定できません。

    前述のように、唯一の違いは配備環境に基づいています。ほとんどの配備環境にはデフォルトのバリデータを使用しますが、1 つの配備環境には「緩和された」実装を使用するようにします。CDI では、注入ポイントに一致する Bean が複数存在する場合でも、あいまい性のエラーを発生させずにパッケージ化できる @Alternative 注釈を使用できます。使用する Bean は、beans.xml に定義します。これにより、同じモジュール内に両方の実装を配備できます。異なるのは beans.xml の定義だけです。この定義は、配備環境によって変更できます。

  16. @Alternative 注釈および対応するインポート文を、RelaxedItemValidator および DefaultItemValidator に追加します。

    エディタで RelaxedItemValidator を開いて、次のように変更します。
    import javax.enterprise.inject.Alternative;
    ...
    
    @Alternative
    public class RelaxedItemValidator implements ItemValidator {
    
        public boolean isValid(Item item) {
            return item.getValue() < (item.getLimit() * 2);
        }
    }

    @Al」を入力してから Ctrl- スペースキーを押して、コード補完を呼び出します。1 つのオプションだけがフィルタされるため、@Alternative 注釈が補完されます。また、対応する javax.enterprise.inject.Alternative のインポート文がファイルの最初に自動的に追加されます。通常は、注釈で Ctrl- スペースキーを押すと Javadoc ドキュメントのポップアップも表示されます。

    エディタの Javadoc ドキュメントのポップアップ

    DefaultItemValidator に切り替え (Ctrl-Tab キーを押します)、次のように変更します。

    import javax.enterprise.inject.Alternative;
    ...
    
    @Alternative
    public class DefaultItemValidator implements ItemValidator {
    
        public boolean isValid(Item item) {
            return item.getValue() < item.getLimit();
        }
    }

    ここでアプリケーションを配備すると「依存関係が満たされない」というエラーが出ますが、これは、一致する Bean を選択肢として 2 つ定義したけれども、beans.xml ファイルでどちらも有効にしていないためです。

  17. IDE の「ファイルに移動」ダイアログを使用すると、すばやく beans.xml を開けます。IDE のメインメニューで「ナビゲート」>「ファイルに移動」(Alt-Shift-O、Mac の場合は Ctrl-Shift-O) を選択してから「beans」と入力します。「了解」をクリックします。「ファイルに移動」ダイアログ
  18. beans.xml ファイルに以下の変更を加えます。
    <beans xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
    
        <alternatives>
            <class>exercise3.RelaxedItemValidator</class>
        </alternatives>
    
    </beans>

    これによって、この配備では RelaxedItemValidator を使用することが CDI に伝えられます。@Alternative 注釈は、Bean を事実上無効にして注釈に使用できないようにする一方で、ほかの Bean とともに実装をパッケージ化できるようにするものと考えることができます。これを beans.xml ファイルに代替として追加すると、事実上、Bean を再度有効にして、注入に使用できるようにします。この種類のメタデータを beans.xml ファイルへ移動することで、さまざまなバージョンのファイルをさまざまな配備環境でバンドルできます。

  19. 「プロジェクトを実行」(「プロジェクトを実行」ボタン) ボタンをクリックするか、F6 (Mac の場合は fn-F6) を押して、プロジェクトを実行します。ブラウザで、ページに表示されている「Execute」ボタンをクリックします。IDE に戻り、出力ウィンドウ (Ctrl-4、Mac の場合は ⌘-4) に表示された GlassFish のサーバーログを調べます。
    INFO: Item =  [Value=34, Limit=7] valid = false
    INFO: Item =  [Value=4, Limit=37] valid = true
    INFO: Item =  [Value=24, Limit=19] valid = true
    INFO: Item =  [Value=89, Limit=32] valid = false

    3 つめの項目に、提供された値 (24) が指定された制限 (19) より大きいにもかかわらず、有効であると表示されています。これにより、RelaxedItemValidator 実装が使用されていることがわかります。


管理対象 Beans へのライフサイクル注釈の適用

この課題では、主 ItemProcessor クラスに ItemErrorHandler を注入します。FileErrorReporterItemErrorHandler インタフェースの唯一の実装であるため、これが注入用に選択されます。クラスのライフサイクル固有のアクションを設定するには、管理対象 Bean の仕様 (JSR 316: Java Platform, Enterprise Edition 6 の仕様に含まれます) から @PostConstruct および @PreDestroy 注釈を使用します。

この課題で作成されるオブジェクトを示す CDI 図

例の続きとして、無効な項目が見つかったときにそれを処理する ItemErrorHandler インタフェースを作成します。

  1. 「プロジェクト」ウィンドウで「exercise3」パッケージを右クリックし、「新規」>「Java インタフェース」を選択します。
  2. 「Java インタフェース」ウィザードで、クラス名として「ItemErrorHandler」、パッケージとして「exercise3」と入力します。「完了」をクリックします。

    新しいインタフェースが生成され、エディタで開かれます。

  3. 引数として Item オブジェクトを取る handleItem() という名前のメソッドを追加します。
    public interface ItemErrorHandler {
        void handleItem(Item item);
    }

    (エディタのヒントを使用して exercise2.Item のインポート文を追加します。)

  4. まず、項目の詳細をファイルに保存する FileErrorReporter という名前の偽のハンドラを持つ ItemErrorHandler を実装します。

    「プロジェクト」ウィンドウで「exercise3」パッケージを右クリックし、「新規」>「Java クラス」を選択します。クラスに「FileErrorReporter」という名前を付け、「完了」をクリックします。

  5. 次のようにして、FileErrorReporterItemErrorHandler を実装し、handleItem() メソッドをオーバーライドします。
    public class FileErrorReporter implements ItemErrorHandler {
    
        @Override
        public void handleItem(Item item) {
            System.out.println("Saving " + item + " to file");
        }
    }

    (エディタのヒントを使用して exercise2.Item のインポート文を追加します。)

    項目の処理を始める前にファイルを開き、処理中は開いたままにしてファイルに内容を追加し、処理が終了したらファイルを閉じるようにします。initProcess() および finishProcess() メソッドをエラーレポータ Bean に手動で追加することもできますが、そうすると呼び出し元がそれらのクラス固有のメソッドについて知る必要があるため、インタフェースへのコードを作成できなくなります。それらの同じメソッドを ItemErrorReporter インタフェースに追加することもできますが、そうするとこのインタフェースを実装するすべてのクラスに、これらのメソッドを不必要に実装する必要があります。代わりに、管理対象 Bean の仕様 (JSR 316: Java Platform, Enterprise Edition 6 の仕様に含まれる) からいくつかのライフサイクル注釈を使用して、Bean ライフサイクルの特定の時点で Bean 上でメソッドを呼び出せます。Bean が構築され、Bean が持つすべての依存関係が注入されると、@PostConstruct 注釈付きメソッドが呼び出されます。同様に、Bean がコンテナによって破棄される直前に @PreDestroy 注釈付きメソッドが呼び出されます。

  6. 次のように、対応する @PostConstruct および @PreDestroy 注釈を持つ init() および release() メソッドを追加します。
    public class FileErrorReporter implements ItemErrorHandler {
    
        @PostConstruct
        public void init() {
            System.out.println("Creating file error reporter");
        }
    
        @PreDestroy
        public void release() {
            System.out.println("Closing file error reporter");
        }
    
        @Override
        public void handleItem(Item item) {
            System.out.println("Saving " + item + " to file");
        }
    }
  7. インポートを修正します。エディタを右クリックして「インポートを修正」を選択するか、Ctrl-Shift-I (Mac の場合は ⌘-Shift-I) を押します。javax.annotation.PostConstruct および javax.annotation.PreDestroy のインポート文がファイルの最初に追加されます。
  8. 最後に、新しい ItemErrorHandler Bean を ItemProcessor に追加します。
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        @Inject @Demo
        private ItemDao itemDao;
    
        @Inject
        private ItemValidator itemValidator;
    
        @Inject
        private ItemErrorHandler itemErrorHandler;
    
        public void execute() {
            List<Item>  items = itemDao.fetchItems();
            for (Item item : items) {
                if (!itemValidator.isValid(item)) {
                    itemErrorHandler.handleItem(item);
                }
            }
        }
    }

    (エディタのヒントを使用して exercise3.ItemErrorHandler のインポート文を追加します。)

  9. 「プロジェクトを実行」(「プロジェクトを実行」ボタン) ボタンをクリックするか、F6 (Mac の場合は fn-F6) を押して、プロジェクトを実行します。ブラウザで、ページに表示されている「Execute」ボタンをクリックします。IDE に戻り、出力ウィンドウ (Ctrl-4、Mac の場合は ⌘-4) に表示された GlassFish のサーバーログを調べます。
    INFO: Creating file error reporter
    INFO: Saving  [Value=34, Limit=7] to file
    INFO: Saving  [Value=89, Limit=32] to file
    INFO: Closing file error reporter

関連項目

異なるアプリケーション配備では、無効な項目の処理のために、項目を拒否したり、個々に通知を送ったり、フラグを付けたり、単に出力ファイルに一覧表示したりするなどの、異なる規則を使用することがあります。また、これらを組み合わせて使用することも考えられます (例: 注文を拒否し、販売担当者に電子メールを送ってから、ファイルに注文を一覧表示する)。この種の多角的な問題の処理に適した方法の 1 つは、イベントを使用する方法です。このシリーズの最終回は CDI イベントについてです:

CDI および Java EE の詳細については、次のリソースを参照してください。