corner imagecorner image
IDEPlatformPluginsDocs & SupportCommunityPartners

CDI のイベントの操作

執筆: 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 入門パート 3 - イベント」というタイトルのブログをベースにしています。ここでは、Java EE のイベントの概念を活用する方法を示します。この方法は、アプリケーション内のイベントの生成およびイベントへの登録 (つまり監視) について、プロデューサとオブザーバの間でコードを分離して管理できます。javax.enterprise.event.Event クラスを使用してイベントを作成し、CDI の @Observes 注釈を使用してイベントに登録します。

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.x
cdiDemo3.zip n/a

注:

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

イベントの利用

前の「@Alternative Bean およびライフサイクル注釈の適用」のチュートリアルでは、項目の一覧を取得してそれを検証し、無効な項目が見つかったら特定のアクションを起こすアプリケーションを作成しました。仮に、今後システムを拡大して、無効な項目が見つかった場合に発生するあらゆることを処理できるようにするとします。これには、電子メールの送信、ほかのデータの変更 (注文の取り消しなど)、またはファイルやデータベース表への拒否リストの格納など、さまざまなものがあります。実装を完全に分離するために、Java EE のイベントを使用できます。イベントは、イベントプロデューサによって生成され、イベントオブザーバから登録されます。ほとんどの CDI と同様にイベントの生成および登録は型保証であるため、限定子は、監視するイベントオブザーバを判別できます。

このシリーズで前のチュートリアルから構築しているアプリケーションを使用した場合、それほど多くの変更をしなくてもこの実装が可能です。項目を処理するたびにイベントを生成する、もう 1 つの ItemErrorHandler (前のチュートリアルで作成) の実装を提供するだけです。このクラスに EventItemHandler という名前を付けて ItemProcessor に注入します。注入対象を選択するには、Notify 限定子を使用します。

この課題で作成されるオブジェクトを示す CDI 図
  1. まず、cdiDemo3.zip ファイル (上記の必要なリソースの一覧表を参照) からサンプルのスタートプロジェクトを抽出します。「ファイル」>「プロジェクトを開く」(Ctrl-Shift-O、Mac の場合は ⌘-Shift-O) を選択してから、コンピュータ上のこのプロジェクトの場所を選択することで、IDE でプロジェクトを開きます。
  2. EventItemHandler という名前のクラスを作成します。「新規ファイル」(「新規ファイル」ボタン) ボタンをクリックするか、Ctrl-N (Mac の場合は ⌘-N) を押してファイルウィザードを開きます。
  3. 「Java」カテゴリから「Java クラス」を選択します。「次へ」をクリックします。
  4. クラス名として「EventItemHandler」、パッケージとして「exercise4」と入力します。
  5. 「完了」をクリックします。新しいクラスおよびパッケージが生成され、エディタでクラスが開きます。
  6. 次のようにして EventItemHandler を実装します。
    public class EventItemHandler implements ItemErrorHandler {
    
        @Inject
        private Event<Item> itemEvent;
    
        @Override
        public void handleItem(Item item) {
            System.out.println("Firing Event");
            itemEvent.fire(item);
        }
    }
    イベントペイロードが Item になる Event のインスタンスを注入します。イベントペイロードとはイベントプロデューサからイベントオブザーバに渡される状態データのことで、この場合は拒否された Item が渡されます。無効な項目が処理されたら、イベントを起動して、受け取った無効な項目を渡します。このイベントベースの項目ハンドラは、ほかの項目ハンドラと同じように注入されるため、いつでも必要なときに交換したり、テスト中に取り換えたりできます。
  7. すべてのインポートを修正します。エディタを右クリックして「インポートを修正」を選択するか、Ctrl-Shift-I (Mac の場合は ⌘-Shift-I) を押します。必ず Event クラスの完全修飾名として javax.enterprise.event.Event を選択するようにしてください。
    「すべてのインポートを修正」ダイアログ

    Event の上で Ctrl- スペースキーを押して、クラスの Javadoc 定義を表示します。上記で使用した fire() メソッドも定義されています。
    エディタの Javadoc ポップアップ
  8. Notify という名前の限定子を作成します (限定子については「CDI の注入および限定子の操作」に記載)。
  9. 「新規ファイル」(「新規ファイル」ボタン) ボタンをクリックするか、Ctrl-N (Mac の場合は ⌘-N) を押してファイルウィザードを開きます。
  10. 「コンテキストと依存関係の注入」カテゴリから「限定子の種類」を選択します。「次へ」をクリックします。
  11. クラス名として「Notify」、パッケージとして「exercise4」と入力します。
  12. 「完了」をクリックします。新しい Notify 限定子がエディタで開きます。
    @Qualifier
    @Retention(RUNTIME)
    @Target({METHOD, FIELD, PARAMETER, TYPE})
    public @interface Notify {
    }
  13. EventItemHandler@Notify 注釈を追加します。
    @Notify
    public class EventItemHandler implements ItemErrorHandler {
    
        ...
    }
    このエラーハンドラを注入のために識別し、ItemProcessor で注入ポイントに追加して使用できる @Notify 限定子注釈を作成しました。
  14. exercise2.ItemProcessor で、EventItemHandler の注入ポイントに @Notify 注釈を追加します。
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        @Inject @Demo
        private ItemDao itemDao;
    
        @Inject
        private ItemValidator itemValidator;
    
        @Inject @Notify
        private ItemErrorHandler itemErrorHandler;
    
        public void execute() {
            List<Item> items = itemDao.fetchItems();
            for (Item item : items) {
                if (!itemValidator.isValid(item)) {
                    itemErrorHandler.handleItem(item);
                }
            }
        }
    }
    (エディタのヒントを使用して exercise4.Notify のインポート文を追加します。)
  15. 「プロジェクトを実行」 (「プロジェクトを実行」ボタン) ボタンをクリックして、プロジェクトを実行します。
  16. ブラウザで「Execute」ボタンをクリックしてから IDE に戻り、「出力」ウィンドウ (Ctrl-4、Mac の場合は ⌘-4) でサーバーログを調べます。構築してきたアプリケーションは、現時点で DefaultItemDao を使用して 4 つの Item を設定してから ItemRelaxedItemValidator を適用するため、itemErrorHandler が 2 度起動するのが確認できるはずです。
    「出力」ウィンドウ - GlassFish サーバーログ
    しかし、現時点ではイベントを監視しているものはありません。これは、@Observes 注釈を使用してオブザーバメソッドを作成すれば修正できます。イベントを監視するために必要な手順はこれだけです。これを示すため、FileErrorReporter (前のチュートリアルで作成) にこの handleItem() メソッドを呼び出すオブザーバメソッドを追加して、起動されたイベントに応答するように変更できます。
  17. FileErrorReporter がイベントに応答するようにするには、クラスに次のメソッドを追加します。
    public class FileErrorReporter implements ItemErrorHandler {
    
        public void eventFired(@Observes Item item) {
            handleItem(item);
        }
    
        ...
    }
    (エディタのヒントを使用して javax.enterprise.event.Observes のインポート文を追加します。)
  18. ふたたびプロジェクトを実行 (F6、Mac の場合は fn-F6) し、「Execute」ボタンをクリックしてから IDE に戻り、「出力」ウィンドウでサーバーログを調べます。
    「出力」ウィンドウ - GlassFish サーバーログ
    先ほどと同じく無効なオブジェクトでイベントが起動されますが、今度は各イベントの起動時に項目の情報が保存されるようになったのが確認できます。また、起動されたイベントごとに FileErrorReporter Bean が作成されて閉じられているため、ライフサイクルイベントが監視されていることもわかります (@PostConstruct@PreDestroy などのライフサイクル注釈については「@Alternative Bean およびライフサイクル注釈の適用」を参照)。

上記の手順で示したように、@Observes 注釈はイベントを監視するための簡単な方法を提供します。

イベントおよびオブザーバは、限定子を使用して注釈を付けることによって、オブザーバが項目の特定のイベントだけを監視するようにもできます。デモについては、「CDI 入門パート 3 - イベント」を参照してください。


スコープの処理

現状のアプリケーションでは、イベントが生成されるたびに FileErrorReporter Bean が作成されます。この場合、項目ごとにファイルを開いて閉じる必要はないため、毎回新しい Bean を作成することは望ましくありません。ただし、プロセスの開始時にファイルを開き、プロセスの完了時にファイルを閉じる必要があります。このために、FileErrorReporter Bean のスコープについて考慮する必要があります。

現時点では、FileErrorReporter Bean に定義されたスコープはありません。定義されたスコープがない場合、CDI はデフォルトの依存擬似スコープを使用します。これは実際のところ、Bean が非常に短い期間 (通常はメソッド呼び出しの期間) で作成および破棄されることを意味します。現在のシナリオでは、起動されたイベントの期間で Bean が作成および破棄されます。これを修正するために、手動でスコープ注釈を追加して Bean のスコープを延ばすことができます。この Bean に @RequestScoped を指定して、最初のイベント起動時に Bean が作成されたら、要求の期間存在し続けるようにします。これはまた、この Bean を注入できるどの注入ポイントにおいても、同じ Bean インスタンスが注入されることを意味します。

  1. FileErrorReporter クラスに、@RequestScope 注釈および対応するjavax.enterprise.context.RequestScoped のインポート文を追加します。
    import javax.enterprise.context.RequestScoped;
    ...
    
    @RequestScoped
    public class FileErrorReporter implements ItemErrorHandler { ... }
    入力中に Ctrl- スペースキーを押すと、エディタのコード補完サポートを呼び出せます。コード補完で項目を選択すると、関連付けられたすべてのインポート文が自動的にクラスに追加されます。
    エディタのコード補完ポップアップ
  2. ふたたびプロジェクトを実行 (F6、Mac の場合は fn-F6) し、「Execute」ボタンをクリックしてから IDE に戻り、「出力」ウィンドウでサーバーログを調べます。
    「出力」ウィンドウ - GlassFish サーバーログ
    FileErrorReporter Bean が最初のイベントの起動時にだけ作成され、最後のイベントの起動後に閉じられます。
    INFO: Firing Event
    INFO: Creating file error reporter
    INFO: Saving  [Value=34, Limit=7] to file
    INFO: Firing Event
    INFO: Saving  [Value=89, Limit=32] to file
    INFO: Closing file error reporter
    

システムの各部分をモジュール式で分離するには、イベントの使用をお勧めします。イベントを使用することで、イベントのオブザーバとプロデューサは互いのことを意識する必要がなくなり、そのための設定の必要もなくなります。イベントのプロデューサにオブザーバを意識させることなく、イベントに登録するコード部分を追加できます (イベントを使用しない場合、通常は手動でイベントのプロデューサにオブザーバを呼び出させる必要があります)。たとえば、だれかが注文の状態を更新したら販売担当者に電子メールを送るイベントや、技術サポートの問題が未解決のまま 1 週間を超えたら顧客担当者に通知するイベントを追加できます。このような種類の規則はイベントを使用しなくても実装できますが、イベントを使用するとビジネスロジックを簡単に分離できるようになります。さらに、コンパイル時や構築時の依存関係がなくなります。ただアプリケーションにモジュールを追加するだけで、自動的にイベントの監視および生成が始まります。


関連項目

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

NetBeans リソース

外部リソース