検索

第7章 コンテキストおよび依存関係の挿入 (CDI)

download PDF

7.1. CDI の概要

7.1.1. コンテキストと依存関係の注入 (CDI)

Contexts and Dependency Injection (CDI) は、Enterprise Java Beans (EJB) 3 コンポーネントを Java Server Faces (JSF) 管理対象 Bean として使用できるよう設計された仕様であり、2 つのコンポーネントモデルを統合し、Java を使用した Web ベースのアプリケーション向けプログラミングモデルを大幅に簡略化します。CDI 1.2 リリースは、1.1 のメンテナンスリリースとして扱われます。 CDI 1.1 の詳細については、JSR 346: Contexts and Dependency Injection for Java™ EE 1.1 を参照してください。

JBoss EAP には、JSR-346:Contexts and Dependency Injection for Java™ EE 1.1 の参照実装である Weld が含まれます。

CDI の利点

CDI には以下のような利点があります。

  • 多くのコードをアノテーションに置き換えることにより、コードベースが単純化および削減されます。
  • 柔軟であり、インジェクションおよびイベントを無効または有効にしたり、代替の Bean を使用したり、非 CDI オブジェクトを簡単にインジェクトしたりできます。
  • デフォルト値と異なるよう設定をカスタマイズする必要がある場合に、オプションで、beans.xmlMETA-INF/ または WEB-INF/ ディレクトリーに含めることができます。
  • パッケージ化とデプロイメントが簡略化され、デプロイメントに追加する必要がある XML の量が減少します。
  • コンテキストを使用したライフサイクル管理が提供されます。インジェクションを要求、セッション、会話、またはカスタムコンテキストに割り当てることができます。
  • 文字列ベースのインジェクションよりも安全かつ簡単にデバッグを行える、タイプセーフな依存関係の注入が提供されます。
  • インターセプターと Bean が切り離されます。
  • 複雑なイベント通知が提供されます。

7.1.2. Weld、Seam 2、および JavaServer Faces 間の関係

Weld は JSR 346: Contexts and Dependency Injection for Java™ EE 1.1 で定義されている CDI の参照実装です。Weld は、Seam 2 と他の依存関係注入フレームワークの影響を受けており、JBoss EAP 6 に含まれています。

Seam 2 の目的は、Enterprise Java Bean と JavaServer Faces 管理対象 Bean を統合することでした。

JavaServer Faces 2.2 では、JSR-344: JavaServer™ Faces 2.2 が実装されます。これは、サーバーサイドユーザーインターフェースをビルドするための API です。

7.2. CDI の有効化

CDI は、JBoss EAP の中核的なテクノロジーの 1 つであり、デフォルトで有効になります。CDI は、設定ファイルの該当するセクションをコメントアウトまたは削除することにより無効になっている場合があります。有効にする必要がある場合は、以下の手順を実行します。

JBoss EAP での CDI の有効化

  1. JBoss EAP を停止します。

    JBoss EAP により実行中に設定ファイルが変更されるため、設定ファイルを直接編集する前にサーバーを停止します。

  2. 適切な設定ファイルを編集します。

    スタンドアロンサーバーの場合は EAP_HOME/standalone/configuration/standalone.xml、管理対象ドメインの場合は EAP_HOME/domain/configuration/domain.xml を編集します。

  3. CDI 拡張機能を追加します。

    org.jboss.as.weld 拡張機能がコメントアウトされた場合は、コメント解除します。全体が削除された場合は、以下の行をファイルの </extensions> タグのすぐ上に新しい行で追加することにより復元します。

    <extension module="org.jboss.as.weld"/>
  4. CDI サブシステムを追加します。

    weld サブシステムがコメントアウトされた場合は、コメント解除します。全体が削除された場合は、以下の行を <profiles> セクションの該当するプロファイルに追加することにより復元します。

    <subsystem xmlns="urn:jboss:domain:weld:3.0"/>
  5. 更新された設定で JBoss EAP を起動します。

JBoss EAP が起動するとき、CDI サブシステムは有効になります。

7.3. CDI を使用したアプリケーションの開発

コンテキストと依存関係の注入 (CDI: Contexts and Dependency Injection) を使用すると、アプリケーションの開発、コードの再利用、デプロイメント時または実行時のコードの調整、およびユニットテストを非常に柔軟に実行できます。JBoss EAP には、CDI の参照実装である Weld が含まれます。これらのタスクは、エンタープライズアプリケーションで CDI を使用する方法を示しています。

7.3.1. デフォルトの Bean 検出モード

bean アーカイブのデフォルトの bean 検出モードは annotated です。このような bean アーカイブは implicit bean archive と呼ばれます。

bean 検出モードが annotated の場合:

  • bean defining annotation がなく、セッション bean の bean クラスでない bean クラスが検出されません。
  • セッション bean 上になく、bean クラスが bean を定義するアノテーションを持たないプロデューサーメソッドが検出されません。
  • セッション bean 上になく、bean クラスが bean を定義するアノテーションを持たないプロデューサーフィールドが検出されません。
  • セッション bean 上になく、bean クラスが bean を定義するアノテーションを持たないディスポーザーメソッドが検出されません。
  • セッション bean 上になく、bean クラスが bean を定義するアノテーションを持たないオブザーバーメソッドが検出されません。
重要

CDI セクションのすべての例は、検出モードが all に設定された場合にのみ有効です。

bean を定義するアノテーション

bean クラスは bean defining annotation を持つことがあり、Bean アーカイブで定義されたようにアプリケーションのどこにでも配置することができます。bean を定義するアノテーションを持つ bean クラスは暗黙的な bean と呼ばれます。

bean を定義するアノテーションのセットには以下のものが含まれます。

  • @ApplicationScoped@SessionScoped@ConversationScoped、および @RequestScoped アノテーション
  • その他すべての通常スコープタイプ
  • @Interceptor および @Decorator アノテーション
  • @Stereotype アノテーションが付けられた stereotype アノテーションすべて
  • @Dependent スコープアノテーション

これらのアノテーションのいずれかが bean クラスで宣言された場合、その bean クラスは bean 定義アノテーションを持っていることになります。たとえば、以下の依存スコープ bean は bean 定義アノテーションを持っています。

@Dependent
public class BookShop
        extends Business
        implements Shop<Book> {
    ...
}
注記

他の JSR-330 実装との互換性を確保するために、@Dependent を除くすべての pseudo-scope アノテーションは bean 定義アノテーションではありません。ただし、pseudo-scope アノテーションを含む stereotype アノテーションは bean 定義アノテーションです。

7.3.2. スキャンプロセスからの Bean の除外

除外フィルターは、Bean アーカイブの beans.xml ファイルの <exclude> 要素によって <scan> 要素の子として定義されます。デフォルトでは、除外フィルターはアクティブです。定義に以下のものが含まれる場合、除外フィルターは非アクティブになります。

  • name 属性を含む、<if-class-available> という名前の子要素。Bean アーカイブのクラスローダーはこの名前のクラスをロードできません。
  • name 属性を含む、<if-class-not-available> という名前の子要素。Bean アーカイブのクラスローダーはこの名前のクラスをロードできます。
  • name 属性を含む、<if-system-property> という名前の子要素。この名前に対して定義されたシステムプロパティーはありません。
  • name 属性と値属性を含む、<if-system-property> という名前の子要素。この名前とこの値に対して定義されたシステムプロパティーはありません。

フィルターがアクティブな場合、タイプは検出から除外され、以下のいずれかの状態になります。

  • 検出されるタイプの完全修飾名が、除外フィルターの名前属性の値に一致します。
  • 検出されるタイプのパッケージ名が、除外フィルターの接尾辞 ".*" を含む名前属性の値に一致します。
  • 検出されるタイプのパッケージ名が、除外フィルターの接尾辞 ".*" を含む名前属性の値で始まります。

たとえば、以下の beans.xml ファイルを考えてみます。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee">

    <scan>
        <exclude name="com.acme.rest.*" /> 1

        <exclude name="com.acme.faces.**"> 2
            <if-class-not-available name="javax.faces.context.FacesContext"/>
        </exclude>

        <exclude name="com.acme.verbose.*"> 3
            <if-system-property name="verbosity" value="low"/>
        </exclude>

        <exclude name="com.acme.ejb.**"> 4
            <if-class-available name="javax.enterprise.inject.Model"/>
            <if-system-property name="exclude-ejbs"/>
        </exclude>
    </scan>

</beans>
1
最初の除外フィルターにより、com.acme.rest パッケージ内のすべてのクラスが除外されます。
2
2 番目の除外フィルターにより、com.acme.faces パッケージとすべてのサブパッケージ内のすべてのクラスが除外されます (JSF が利用可能でない場合のみ)。
3
3 番目の除外フィルターにより、システムプロパティー verbosity が値 low を持つ場合に、com.acme.verbose パッケージ内のすべてのクラスが除外されます。
4
4 番目の除外フィルターにより、システムプロパティー exclude-ejbs が任意の値で設定され、javax.enterprise.inject.Model クラスがクラスローダーでも利用可能な場合に、com.acme.ejb パッケージとすべてのサブパッケージ内のすべてのクラスが除外されます。
注記

Java EE コンポーネントが Bean と見なされないように、Java EE コンポーネントは @Vetoed でアノテートすることが安全です。イベントは @Vetoed でアノテートされたタイプに対して、または @Vetoed でアノテートされたパッケージで発生しません。詳細については、@Vetoed を参照してください。

7.3.3. インジェクションを使用した実装の拡張

インジェクションを使用して、既存のコードの機能を追加または変更できます。

この例では、既存のクラスに翻訳機能を追加します。メソッド buildPhrase を持つ Welcome クラスがすでにあることを前提とします。buildPhrase メソッドは、都市の名前を引数として取得し、"Welcome to Boston!" などのフレーズを出力します。

例: Translator Bean を Welcome クラスにインジェクトする

以下のコードにより、想像上の Translator オブジェクトが Welcome クラスにインジェクトされます。Translator オブジェクトは、文をある言語から別の言語に翻訳できる EJB ステートレス Bean または別のタイプの Bean になります。この例では、Translator は挨拶全体を翻訳するために使用され、元の Welcome クラスは変更されません。Translator は、buildPhrase メソッドが呼び出される前にインジェクトされます。

public class TranslatingWelcome extends Welcome {

    @Inject Translator translator;

    public String buildPhrase(String city) {
        return translator.translate("Welcome to " + city + "!");
    }
    ...
}

7.4. あいまいな依存関係または満たされていない依存関係

コンテナーが 1 つの Bean への注入を解決できない場合、依存関係があいまいとなります。

コンテナーがいずれの Bean に対しても注入の解決をできない場合、依存関係が満たされなくなります。

コンテナーは以下の手順を踏み、依存関係の解決をはかります。

  1. インジェクションポイントの Bean 型を実装する全 Bean にある修飾子アノテーションを解決します。
  2. 無効となっている Bean をフィルタリングします。無効な Bean とは、明示的に有効化されていない @Alternative Bean のことです。

依存関係があいまいな場合、あるいは満たされない場合は、コンテナーはデプロイメントを中断して例外を発生させます。

あいまいな依存関係を修正するには、修飾子を使用したあいまいなインジェクションの解決を参照してください。

7.4.1. 修飾子

修飾子は、コンテナーが複数の Bean を解決できるときにあいまいな依存関係を回避するために使用されるアノテーションであり、インジェクションポイントに含められます。インジェクションポイントで宣言された修飾子は、同じ修飾子を宣言する有効な Bean セットを提供します。

修飾子は、以下の例で示されたように Retention と Target を使用して宣言する必要があります。

例: @Synchronous 修飾子と @Asynchronous 修飾子の定義

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Synchronous {}

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Asynchronous {}

例: @Synchronous 修飾子と @Asynchronous 修飾子の使用

@Synchronous
public class SynchronousPaymentProcessor implements PaymentProcessor {
   public void process(Payment payment) { ... }
}

@Asynchronous
public class AsynchronousPaymentProcessor implements PaymentProcessor {
   public void process(Payment payment) { ... }
}
'@Any'

Bean またはインジェクションポイントにより修飾子が明示的に宣言されない場合、コンテナーにより修飾子は @Default と見なされます。場合によっては、修飾子を指定せずにインジェクションポイントを宣言する必要があります。この場合も修飾子が存在します。すべての Bean には修飾子 @Any が含まれます。したがって、インジェクションポイントで @Any を明示的に指定することにより、インジェクションが可能な Bean を制限せずにデフォルトの修飾子を抑制できます。

これは、特定の Bean タイプを持つすべての Bean に対して繰り返し処理を行う場合に特に役に立ちます。

import javax.enterprise.inject.Instance;
...

@Inject

void initServices(@Any Instance<Service> services) {

   for (Service service: services) {

      service.init();

   }

}

各 Bean は修飾子 @Any を持ちます (この修飾子が明示的に指定されていない場合でも)。

各イベントも修飾子 @Any を持ちます (この修飾子を明示的に宣言せずにイベントが発生した場合でも)。

@Inject @Any Event<User> anyUserEvent;

@Any 修飾子により、インジェクションポイントが特定の Bean タイプのすべての Bean またはイベントを参照できます。

@Inject @Delegate @Any Logger logger;

7.4.2. 修飾子を使用したあいまいなインジェクションの解決

修飾子を使用してあいまいなインジェクションを解決できます。あいまいなインジェクションについては、あいまいな依存関係または満たされていない依存関係をお読みください。

以下の例はあいまいであり、Welcome の 2 つの実装 (翻訳を行う 1 つと翻訳を行わないもう 1 つ) を含みます。翻訳を行う Welcome を使用するには、インジェクションを指定する必要があります。

例: あいまいなインジェクション

public class Greeter {
  private Welcome welcome;

  @Inject
  void init(Welcome welcome) {
    this.welcome = welcome;
  }
  ...
}

修飾子を使用したあいまいなインジェクションの解決
  1. あいまいなインジェクションを解決するには、@Translating という名前の修飾子アノテーションを作成します。

    @Qualifier
    @Retention(RUNTIME)
    @Target({TYPE,METHOD,FIELD,PARAMETERS})
    public @interface Translating{}
  2. @Translating アノテーションを使用して、翻訳を行う Welcome をアノテートします。

    @Translating
    public class TranslatingWelcome extends Welcome {
        @Inject Translator translator;
        public String buildPhrase(String city) {
            return translator.translate("Welcome to " + city + "!");
        }
        ...
    }
  3. インジェクションで翻訳を行う Welcome を要求します。ファクトリーメソッドパターンの場合と同様に、修飾された実装を明示的に要求する必要があります。あいまいさはインジェクションポイントで解決されます。

    public class Greeter {
      private Welcome welcome;
      @Inject
      void init(@Translating Welcome welcome) {
        this.welcome = welcome;
      }
      public void welcomeVisitors() {
        System.out.println(welcome.buildPhrase("San Francisco"));
      }
    }

7.5. 管理 Bean

Java EE では管理対象 Bean 仕様の共通定義が確立されています。管理対象 Bean は、プログラミングの制限が最小限であるコンテナー管理オブジェクトとして定義され、POJO (Plain Old Java Object) として知られるようになりました。管理対象 Bean はリソースのインジェクション、ライフサイクルコールバック、インターセプターなどの基本サービスの小さなセットをサポートします。EJBや CDI などのコンパニオン仕様は、この基本モデルに基づいて構築されます。

ごくわずかな例外を除き、パラメーターのないコンストラクター (または @Inject アノテーションが指定されたコンストラクター) を持つ具象 Java クラスは Bean になります。これには、すべての Java Bean と EJB セッション Bean が含まれます。

7.5.1. Bean であるクラスのタイプ

管理対象 Bean は Java クラスです。管理対象 Bean の基本的なライフサイクルやセマンティクスは、管理対象 Bean の仕様で定義されています。Bean クラス @ManagedBean をアノテートすることで明示的に管理対象 Bean を宣言できますが、CDI ではその必要はありません。この仕様によると、CDI コンテナーでは、以下の条件を満たすクラスはすべて管理対象 Bean として扱われます。

  • 非静的な内部クラスではないこと。
  • 具象クラス、あるいは @Decorator アノテーションが付与されている。
  • EJB コンポーネントを定義するアノテーションが付与されていないこと、あるいは ejb-jar.xml で EJB Bean クラスとして宣言されていること。
  • インターフェース javax.enterprise.inject.spi.Extension が実装されていないこと。
  • パラメーターのないコンストラクターか、@Inject アノテーションが付与されたコンストラクターがあること。
  • アノテートされた @Vetoed でないこと、または @Vetoed でアノテートされたパッケージ内にないこと。

管理対象 Bean の Bean 型で無制限のものには、直接的あるいは間接的に実装する Bean クラス、全スーパークラス、および全インターフェースが含まれます。

管理対象 Bean にパブリックフィールドがある場合は、デフォルトの @Dependent スコープが必要です。

@Vetoed

CDI 1.1 には、新しいアノテーションである @Vetoed が導入されました。このアノテーションを追加することにより、Bean をインジェクションから除外できます。

@Vetoed
public class SimpleGreeting implements Greeting {
    ...
}

このコードでは、SimpleGreeting Bean はインジェクションの対象となりません。

パッケージ内のすべての Bean をインジェクションから除外できます。

@Vetoed
package org.sample.beans;

import javax.enterprise.inject.Vetoed;

org.sample.beans パッケージ内の package-info.java のこのコードにより、このパッケージ内のすべての Bean がインジェクションから除外されます。

ステートレス EJB や JAX-RS リソースエンドポイントなどの Java EE コンポーネントは、Bean と見なされないように @Vetoed でマークできます。@Vetoed アノテーションをすべての永続エンティティーに追加すると、BeanManager がエンティティーを CDI Bean として管理することを回避できます。エンティティーが @Vetoed とアノテートされた場合は、インジェクションが行われません。この理由は、JPA プロバイダーが破損する原因となる操作を BeanManager が実行することを防ぐことです。

7.5.2. CDI を用いたオブジェクトの Bean へのインジェクト

CDI コンポーネントがアプリケーションで検出されると、CDI は自動的にアクティベートされます。デフォルト値と異なるよう設定をカスタマイズする場合は、デプロイメントアーカイブに META-INF/beans.xml または WEB-INF/beans.xml を含めることができます。

他のオブジェクトにオブジェクトをインジェクトする
  1. クラスのインスタンスを取得するには、Bean 内で @Inject を使用してフィールドをアノテートします。

    public class TranslateController {
       @Inject TextTranslator textTranslator;
       ...
  2. インジェクトしたオブジェクトのメソッドを直接使用します。TextTranslator にメソッド translate があることを前提とします。

    // in TranslateController class
    
    public void translate() {
       translation = textTranslator.translate(inputText);
    }
  3. Bean のコンストラクターでインジェクションを使用します。ファクトリーやサービスロケーターを使用して作成する代わりに、Bean のコンストラクターへオブジェクトをインジェクトできます。

    public class TextTranslator {
    
       private SentenceParser sentenceParser;
       private Translator sentenceTranslator;
    
       @Inject
       TextTranslator(SentenceParser sentenceParser, Translator sentenceTranslator) {
          this.sentenceParser = sentenceParser;
          this.sentenceTranslator = sentenceTranslator;
       }
    
       // Methods of the TextTranslator class
       ...
    }
  4. Instance(<T>) インターフェースを使用してインスタンスをプログラムにより取得します。Bean 型でパラメーター化されると、Instance インターフェースは TextTranslator のインスタンスを返すことができます。

    @Inject Instance<TextTranslator> textTranslatorInstance;
    ...
    public void translate() {
       textTranslatorInstance.get().translate(inputText);
    }

オブジェクトを Bean にインジェクトすると、Bean は全オブジェクトのメソッドとプロパティーを使用できるようになります。Bean のコンストラクターにインジェクトするときに、インジェクションがすでに存在するインスタンスを参照する場合以外は、Bean のコンストラクターが呼び出されるとインジェクトされたオブジェクトのインスタンスが作成されます。たとえば、セッションの存続期間内にセッションスコープの Bean をインジェクトしても、新しいインスタンスは作成されません。

7.6. コンテキストおよびスコープ

CDI では、特定のスコープに関連付けられた Bean のインスタンスを保持するストレージ領域をコンテキストと呼びます。

A scope is the link between a bean and a context. A scope/context combination may have a specific lifecycle. Several predefined scopes exist, and you can create your own. Examples of predefined scopes are @RequestScoped, @SessionScoped, and @ConversationScope.

表7.1 利用可能なスコープ
範囲説明

@Dependent

Bean は、参照を保持する Bean のライフサイクルにバインドされます。インジェクション Bean のデフォルトのスコープは @Dependent です。

@ApplicationScoped

Bean はアプリケーションのライフサイクルにバインドされます。

@RequestScoped

Bean はリクエストのライフサイクルにバインドされます。

@SessionScoped

Bean はセッションのライフサイクルにバインドされます。

@ConversationScoped

Bean は会話のライフサイクルにバインドされます。会話スコープは、リクエストの長さとセッションの間であり、アプリケーションによって制御されます。

カスタムスコープ

上記のコンテキストで対応できない場合は、カスタムスコープを定義できます。

7.7. 名前付き Bean

Bean には、@Named アノテーションを使用して名前を付けることができます。Bean を命名することにより、Bean を Java Server Faces (JSF) と Expression Language (EL) で直接使用できるようになります。

@Named アノテーションは、Bean 名であるオプションパラメーターを取ります。このパラメーターが省略された場合、Bean 名はデフォルトで最初の文字が小文字に変換された Bean のクラス名に設定されます。

7.7.1. 名前付き Bean の使用

@Named Annotation を使用した Bean 名の設定
  1. @Named アノテーションを使用して名前を Bean に割り当てます。

    @Named("greeter")
    public class GreeterBean {
      private Welcome welcome;
    
      @Inject
      void init (Welcome welcome) {
        this.welcome = welcome;
      }
    
      public void welcomeVisitors() {
        System.out.println(welcome.buildPhrase("San Francisco"));
      }
    }

    上記の例では、名前が指定されていない場合、デフォルトの名前は greeterBean になります。

  2. JSF ビューで名前付き Bean を使用します。

    <h:form>
      <h:commandButton value="Welcome visitors" action="#{greeter.welcomeVisitors}"/>
    </h:form>

7.8. Bean ライフサイクル

このタスクは、リクエストの残存期間の間 Bean を保存する方法を示しています。

インジェクトされた Bean のデフォルトのスコープは @Dependent です。つまり、Bean のライフサイクルは、参照を保持する Bean のライフサイクルに依存します。他の複数のスコープが存在し、独自のスコープを定義できます。詳細については、コンテキストおよびスコープを参照してください。

Bean ライフサイクルの管理

  1. 必要なスコープで Bean をアノテートします。

    @RequestScoped
    @Named("greeter")
    public class GreeterBean {
      private Welcome welcome;
      private String city; // getter & setter not shown
      @Inject   void init(Welcome welcome) {
        this.welcome = welcome;
      }
      public void welcomeVisitors() {
        System.out.println(welcome.buildPhrase(city));
      }
    }
  2. Bean が JSF ビューで使用されると、Bean は状態を保持します。

    <h:form>
      <h:inputText value="#{greeter.city}"/>
      <h:commandButton value="Welcome visitors" action="#{greeter.welcomeVisitors}"/>
    </h:form>

Bean は、指定するスコープに関連するコンテキストに保存され、スコープが適用される限り存続します。

7.8.1. プロデューサーメソッドの使用

プロデューサーメソッドは、Bean インスタンスのソースとして動作するメソッドです。指定されたコンテキストにインスタンスが存在しない場合は、メソッド宣言自体で Bean が定義され、コンテナーによって Bean のインスタンスを取得するメソッドが呼び出されます。プロデューサーメソッドにより、アプリケーションは Bean インスタンス化プロセスを完全に制御できるようになります。

このタスクは、インジェクション用の Bean ではないさまざまなオブジェクトを生成するプロデューサーメソッドを使用する方法を示しています。

例: 代替の代わりにプロデューサーメソッドを使用してデプロイメント後のポリモーフィズムを可能にする

例の @Preferred アノテーションは、修飾子アノテーションです。修飾子の詳細については、修飾子を参照してください。

@SessionScoped
public class Preferences implements Serializable {
   private PaymentStrategyType paymentStrategy;
   ...
   @Produces @Preferred
   public PaymentStrategy getPaymentStrategy() {
       switch (paymentStrategy) {
           case CREDIT_CARD: return new CreditCardPaymentStrategy();
           case CHECK: return new CheckPaymentStrategy();
           default: return null;
       }
   }
}

以下のインジェクションポイントは、プロデューサーメソッドと同じタイプおよび修飾子アノテーションを持つため、通常の CDI インジェクションルールを使用してプロデューサーメソッドに解決されます。プロデューサーメソッドは、このインジェクションポイントを処理するインスタンスを取得するためにコンテナーにより呼び出されます。

@Inject @Preferred PaymentStrategy paymentStrategy;

例: スコープをプロデューサーメソッドに割り当てる

プロデューサーメソッドのデフォルトのスコープは @Dependent です。スコープを Bean に割り当てた場合、スコープは適切なコンテキストにバインドされます。この例のプロデューサーメソッドは、1 つのセッションあたり一度だけ呼び出されます。

@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy() {
   ...
}

例: プロデューサーメソッド内部でのインジェクションの使用

アプリケーションにより直接インスタンス化されたオブジェクトは、依存関係の注入を利用できず、インターセプターを持ちません。ただし、プロデューサーメソッドへの依存関係の注入を使用して Bean インスタンスを取得できます。

@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps,
                                          CheckPaymentStrategy cps ) {
   switch (paymentStrategy) {
      case CREDIT_CARD: return ccps;
      case CHEQUE: return cps;
      default: return null;
   }
}

リクエストスコープの Bean をセッションスコープのプロデューサーにインジェクトする場合は、プロデューサーメソッドにより、現在のリクエストスコープのインスタンスがセッションスコープにプロモートされます。これは、適切な動作ではないため、プロデューサーメソッドをこのように使用する場合は注意してください。

注記

プロデューサーメソッドのスコープは、プロデューサーメソッドを宣言する Bean から継承されません。

プロデューサーメソッドを使用して、Bean ではないオブジェクトをインジェクトし、コードを動的に変更できます。

7.9. 代替の Bean

実装が特定のクライアントモジュールまたはデプロイメントシナリオに固有である Bean が代替となります。

デフォルトでは、@Alternative Bean が無効になります。これらは、beans.xml ファイルを編集することにより、特定の Bean アーカイブに対して有効になります。ただし、このアクティベーションは、そのアーカイブの Bean に対してのみ適用されます。CDI 1.1 以降、代替の Bean は、@Priority アノテーションを使用してアプリケーション全体に対して有効にできます。

代替の定義例

この代替により、@Synchronous 代替と@Asynchronous 代替を使用して PaymentProcessor クラスの実装が定義されます。

@Alternative @Synchronous @Asynchronous

public class MockPaymentProcessor implements PaymentProcessor {

   public void process(Payment payment) { ... }

}

beans.xml@Alternative を有効にする例

<beans
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">
   <alternatives>
         <class>org.mycompany.mock.MockPaymentProcessor</class>
   </alternatives>
</beans>

選択された代替の宣言

@Priority アノテーションにより、アプリケーション全体に対して代替を有効にすることができるようになります。代替にはアプリケーションの優先度を割り当てることができます。

  • 管理対象 Bean またはセッション Bean の Bean クラスに @Priority アノテーションを置く、または
  • プロデューサーメソッド、フィールド、またはリソースを宣言する Bean クラスに @Priority アノテーションを置く

7.9.1. 代替を用いたインジェクションのオーバーライド

代替の Bean を使用すると、既存の Bean をオーバーライドできます。これらは、同じ役割を満たすクラスをプラグインする方法として考慮できますが、動作が異なります。代替の Bean はデフォルトで無効になります。

このタスクは、代替を指定し、有効にする方法を示しています。

インジェクションのオーバーライド

このタスクでは、プロジェクトに TranslatingWelcome クラスがすでにあることを前提としています。ただし、これを "mock" TranslatingWelcome クラスでオーバーライドするとします。これは、実際の Translator Bean を使用できないテストデプロイメントのケースに該当します。

  1. 代替を定義します。

    @Alternative
    @Translating
    public class MockTranslatingWelcome extends Welcome {
      public String buildPhrase(string city) {
        return "Bienvenue à " + city + "!");
      }
    }
  2. 置換実装をアクティベートするために、完全修飾クラス名を META-INF/beans.xml または WEB-INF/beans.xml ファイルに追加します。

    <beans>
      <alternatives>
        <class>com.acme.MockTranslatingWelcome</class>
      </alternatives>
    </beans>

元の実装の代わりに代替実装が使用されます。

7.10. ステレオタイプ

多くのシステムでは、アーキテクチャーパターンを使用して繰り返し発生する Bean ロールのセットを生成します。ステレオタイプを使用すると、このようなロールを指定し、中心的な場所で、このロールを持つ Bean に対する共通メタデータを宣言できます。

ステレオタイプにより、以下のいずれかの組み合わせがカプセル化されます。

  • デフォルトスコープ
  • インターセプターバインディングのセット

また、ステレオタイプにより、以下の 2 つのいずれかを指定できます。

  • ステレオタイプがデフォルトの Bean EL 名であるすべての Bean
  • ステレオタイプが代替であるすべての Bean

Bean は、ステレオタイプを 0 個以上宣言できます。ステレオタイプは、他の複数のアノテーションをパッケージ化する @Stereotype アノテーションです。ステレオタイプアノテーションは、Bean クラス、プロデューサーメソッド、またはフィールドに適用できます。

ステレオタイプからスコープを継承するクラスは、そのステレオタイプをオーバーライドし、Bean で直接スコープを指定できます。

また、ステレオタイプが @Named アノテーションを持つ場合、配置された Bean はデフォルトの Bean 名を持ちます。この Bean は、@Named アノテーションが Bean で直接指定された場合に、この名前をオーバーライドできます。名前付き Bean の詳細については、名前付き Beanを参照してください。

7.10.1. ステレオタイプの使用

ステレオタイプを使用しないと、アノテーションが煩雑になる可能性があります。このタスクは、ステレオタイプを使用して煩雑さとコードを減らす方法を示しています。

例: アノテーションの煩雑さ

@Secure
@Transactional
@RequestScoped
@Named
public class AccountManager {
  public boolean transfer(Account a, Account b) {
    ...
  }
}

ステレオタイプの定義および使用
  1. ステレオタイプを定義します。

    @Secure
    @Transactional
    @RequestScoped
    @Named
    @Stereotype
    @Retention(RUNTIME)
    @Target(TYPE)
    public @interface BusinessComponent {
     ...
    }
  2. ステレオタイプを使用します。

    @BusinessComponent
    public class AccountManager {
      public boolean transfer(Account a, Account b) {
        ...
      }
    }

7.11. オブザーバーメソッド

オブザーバーメソッドは、イベント発生時に通知を受け取ります。

また、CDI は、イベントが発生したトランザクションの完了前または完了後フェーズ中にイベント通知を受け取るトランザクションオブザーバーメソッドを提供します。

7.11.1. イベントの発生と確認

例: イベントの発生

以下のコードは、メソッドでインジェクトおよび使用されるイベントを示しています。

public class AccountManager {
  @Inject Event<Withdrawal> event;

  public boolean transfer(Account a, Account b) {
    ...
    event.fire(new Withdrawal(a));
  }
}

例: 修飾子を使用したイベントの発生

修飾子を使用すると、より具体的にイベントのインジェクションにアノテーションを付けられます。修飾子の詳細については、修飾子を参照してください。

public class AccountManager {
  @Inject @Suspicious Event <Withdrawal> event;

  public boolean transfer(Account a, Account b) {
    ...
    event.fire(new Withdrawal(a));
  }
}

例: イベントの確認

イベントを確認するには、@Observes アノテーションを使用します。

public class AccountObserver {
  void checkTran(@Observes Withdrawal w) {
    ...
  }
}

修飾子を使用すると、特定の種類のイベントのみを確認できます。

public class AccountObserver {
  void checkTran(@Observes @Suspicious Withdrawal w) {
    ...
  }
}

7.11.2. トランザクションオブザーバー

トランザクションオブザーバーは、イベントが発生したトランザクションの完了フェーズ前または完了フェーズ後にイベント通知を受け取ります。トランザクションオブザーバーは、単一のアトミックトランザクションよりも状態が保持される期間が長いため、トランザクションオブザーバーはステートフルオブジェクトモデルで重要になります。

トラザクションオブザーバーには 5 つの種類があります。

  • IN_PROGRESS: デフォルトではオブザーバーは即座に呼び出されます。
  • AFTER_SUCCESS: トランザクションが正常に完了する場合のみ、オブザーバーはトランザクションの完了フェーズの後に呼び出されます。
  • AFTER_FAILURE: トランザクションの完了に失敗する場合のみ、オブザーバーはトランザクションの完了フェーズの後に呼び出されます。
  • AFTER_COMPLETION: オブザーバーはトランザクションの完了フェーズの後に呼び出されます。
  • BEFORE_COMPLETION: オブザーバーはトランザクションの完了フェーズの前に呼び出されます。

以下のオブザーバーメソッドは、カテゴリーツリーを更新するトランザクションが正常に実行される場合のみアプリケーションコンテキストにキャッシュされたクエリー結果セットを更新します。

public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }

アプリケーションスコープで JPA クエリー結果セットをキャッシュしたことを仮定します。

import javax.ejb.Singleton;
import javax.enterprise.inject.Produces;

@ApplicationScoped @Singleton

public class Catalog {
   @PersistenceContext EntityManager em;
   List<Product> products;
   @Produces @Catalog
   List<Product> getCatalog() {
      if (products==null) {
         products = em.createQuery("select p from Product p where p.deleted = false")
            .getResultList();
      }
      return products;
   }
}

Product はときどき作成および削除されます。Product が作成または削除されると Product カタログを更新する必要がありますが、トランザクションが正常に完了した後に更新を行う必要があります。

以下は、イベントを引き起こす Products を作成および削除する Bean の例になります。

import javax.enterprise.event.Event;

@Stateless

public class ProductManager {
   @PersistenceContext EntityManager em;
   @Inject @Any Event<Product> productEvent;
   public void delete(Product product) {
      em.delete(product);
      productEvent.select(new AnnotationLiteral<Deleted>(){}).fire(product);
   }

   public void persist(Product product) {
      em.persist(product);
      productEvent.select(new AnnotationLiteral<Created>(){}).fire(product);
   }
   ...
}

トランザクションが正常に完了した後に、Catalog がイベントを監視できるようになりました。

import javax.ejb.Singleton;

@ApplicationScoped @Singleton
public class Catalog {
   ...
   void addProduct(@Observes(during = AFTER_SUCCESS) @Created Product product) {
      products.add(product);
   }

   void removeProduct(@Observes(during = AFTER_SUCCESS) @Deleted Product product) {
      products.remove(product);
   }

}

7.12. インターセプター

インターセプターを使用すると、Bean のメソッドを直接変更せずに Bean のビジネスメソッドに機能を追加できます。インターセプターは、Bean のビジネスメソッドの前に実行されます。インターセプターは、JSR 318: Enterprise JavaBeans™ 3.1 仕様の一部として定義されています。

CDI により、インターセプターと Bean をバインドするアノテーションを利用できるため、この機能が強化されます。

インターセプションポイント

  • ビジネスメソッドインターセプション: ビジネスメソッドのインターセプターは、Bean のクライアントによる Bean のメソッド呼び出しに適用されます。
  • ライフサイクルコールバックインターセプション: ライフサイクルコールバックインターセプションは、コンテナーによるライフサイクルコールバックの呼び出しに適用されます。
  • タイムアウトメソッドインターセプター: タイムアウトメソッドインターセプターは、コンテナーによる EJB タイムアウトメソッドの呼び出しに適用されます。

インターセプターの有効化

デフォルトでは、すべてのインターセプターが無効になります。インターセプターは、Bean アーカイブの beans.xml 記述子を使用して有効にすることができます。ただし、このアクティベーションは、そのアーカイブの Bean に対してのみ適用されます。CDI 1.1 以降、インターセプターは、@Priority アノテーションを使用してアプリケーション全体に対して有効にできます。

<beans
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">
   <interceptors>
      <class>org.mycompany.myapp.TransactionInterceptor</class>
   </interceptors>
</beans>

XML 宣言を使用すると、以下の 2 つのことが可能になります。

  • システムでインターセプターの順序を指定して、決定論的な動作を得ることができます。
  • デプロイメント時にインターセプタークラスを有効または無効にすることができます。

@Priority を使用して有効にされたインターセプターは、beans.xml ファイルを使用して有効にされたインターセプターよりも前に呼び出されます。

注記

インターセプターが @Priority により有効にされ、同時に beans.xml により呼び出されると、移植不可能な動作になります。したがって、異なる CDI 実装で整合性のある動作を維持するには、この組み合わせの有効化を回避する必要があります。

7.12.1. CDI とのインターセプターの使用

CDI により、インターセプターコードが単純化され、ビジネスコードへの適用が簡単になります。

CDI がない場合、インターセプターには 2 つの問題があります。

  • Bean は、インターセプター実装を直接指定する必要があります。
  • アプリケーションの各 Bean は、インターセプターの完全なセットを適切な順序で指定する必要があります。この場合、アプリケーション全体でインターセプターを追加または削除するには時間がかかり、エラーが発生する傾向があります。

例: CDI のないインターセプター

@Interceptors({
  SecurityInterceptor.class,
  TransactionInterceptor.class,
  LoggingInterceptor.class
})
@Stateful public class BusinessComponent {
  ...
}

CDI とのインターセプターの使用
  1. インターセプターバインディングタイプを定義します。

    @InterceptorBinding
    @Retention(RUNTIME)
    @Target({TYPE, METHOD})
    public @interface Secure {}
  2. インターセプター実装をマークします。

    @Secure
    @Interceptor
    public class SecurityInterceptor {
      @AroundInvoke
      public Object aroundInvoke(InvocationContext ctx) throws Exception {
        // enforce security ...
        return ctx.proceed();
        }
    }
  3. ビジネスコードでインターセプターを使用します。

    @Secure
    public class AccountManager {
      public boolean transfer(Account a, Account b) {
        ...
      }
    }
  4. インターセプターを META-INF/beans.xml または WEB-INF/beans.xml に追加することにより、インターセプターをデプロイメントで有効にします。

    <beans>
      <interceptors>
        <class>com.acme.SecurityInterceptor</class>
        <class>com.acme.TransactionInterceptor</class>
      </interceptors>
    </beans>

インターセプターは、リストされた順序で適用されます。

7.13. デコレーター

デコレーターは、特定の Java インターフェースからの呼び出しをインターセプトし、そのインターフェースに割り当てられたすべてのセマンティクスを認識します。デコレーターは、何らかの業務をモデル化するのに役に立ちますが、インターセプターの一般性を持ちません。デコレーターは Bean または抽象クラスであり、デコレートするタイプを実装し、@Decorator アノテーションが付けられます。CDI アプリケーションでデコレーターを呼び出すには、beans.xml ファイルで指定する必要があります。

beans.xml でデコレーターを呼び出す例

<beans
   xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
      http://xmlns.jcp.org/xml/ns/javaee
      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">
   <decorators>
         <class>org.mycompany.myapp.LargeTransactionDecorator</class>
   </decorators>
</beans>

この宣言には 2 つの主な目的があります。

  • システムでデコレーターの順序を指定して、決定論的な動作を得ることができます。
  • デプロイメント時にデコレータークラスを有効または無効にすることができます。

デコレートされたオブジェクトへの参照を取得するために、デコレーターには @Delegate インジェクションポイントが 1 つ必要になります。

デコレーターの例

@Decorator
public abstract class LargeTransactionDecorator implements Account {

   @Inject @Delegate @Any Account account;
   @PersistenceContext EntityManager em;

   public void withdraw(BigDecimal amount) {
      ...
   }

   public void deposit(BigDecimal amount);
      ...
   }
}

CDI 1.1 以降、デコレーターは、@Priority アノテーションを使用してアプリケーション全体に対して有効にできます。

@Priority を使用して有効にされたデコレーターは、beans.xml を使用して有効にされたデコレーターよりも前に呼び出されます。低い優先度値が最初に呼び出されます。

注記

デコレーターが @Priority により有効にされ、同時に beans.xml により呼び出されると、移植不可能な動作になります。したがって、異なる CDI 実装で整合性のある動作を維持するには、この組み合わせの有効化を回避する必要があります。

7.14. 移植可能な拡張機能

CDI は、フレームワーク、拡張機能、および他のテクノロジーとの統合の基礎となることを目的としています。したがって、CDI は、移植可能な CDI の拡張機能の開発者が使用する SPI のセットを公開します。

拡張機能は、以下のような種類の機能を提供できます。

  • ビジネスプロセス管理エンジンとの統合
  • Spring、Seam、GWT、Wicket などのサードパーティーフレームワークとの統合
  • CDI プログラミングモデルに基づく新しいテクノロジー

JSR-346 仕様に基づいて、移植可能な拡張機能は次の方法でコンテナーと統合できます。

  • 独自の Bean、インターセプター、およびデコレーターをコンテナーに提供します。
  • 依存関係注入サービスを使用した独自のオブジェクトへの依存関係のインジェクション
  • カスタムスコープのコンテキスト実装を提供します。
  • アノテーションベースのメタデータを別のソースからのメタデータで拡大またはオーバーライドします。

7.15. Bean プロキシ

通常、インジェクトされた Bean のクライアントは Bean インスタンスへの直接参照を保持しません。Bean が依存オブジェクト (スコープ @Dependent) でない場合、コンテナーはプロキシオブジェクトを使用して、インジェクトされたすべての参照を Bean にリダイレクトする必要があります。

この Bean プロキシはクライアントプロキシと呼ばれ、メソッド呼び出しを受け取る Bean インスタンスが、現在のコンテキストと関連するインスタンスになるようにします。またクライアントプロキシは、他のインジェクトされた Bean を再帰的にシリアライズせずに、セッションコンテキストなどのコンテキストにバインドされる Bean をディスクへシリアライズできるようにします。

Java の制限により、コンテナーによるプロキシの作成が不可能な Java の型があります。これらの型の 1 つで宣言されたインジェクションポイントが、@Dependent 以外のスコープを持つ Bean に解決すると、コンテナーがデプロイメントをアボートします。

特定の Java の型ではコンテナーによってプロキシを作成できません。これらの型には次のようなものがあります。

  • パラメーターのない非プライベートコンストラクターを持たないクラス
  • final が宣言されたクラスまたは final メソッドを持つクラス
  • 配列およびプリミティブ型

7.16. インジェクションでのプロキシの使用

各 Bean のライフサイクルが異なる場合に、インジェクションにプロキシが使用されます。プロキシは起動時に作成された Bean のサブクラスで、Bean クラスのプライベートメソッド以外のメソッドをすべて上書きします。プロキシは実際の Bean インスタンスへ呼び出しを転送します。

この例では、PaymentProcessor インスタンスは直接 Shop へインジェクトされません。その代わりにプロキシがインジェクトされ、processPayment() メソッドが呼び出されるとプロキシが現在の PaymentProcessor Bean インスタンスをルックアップし、processPayment() メソッドを呼び出します。

例: プロキシインジェクション

@ConversationScoped
class PaymentProcessor
{
  public void processPayment(int amount)
  {
    System.out.println("I'm taking $" + amount);
  }
}

@ApplicationScoped
public class Shop
{

  @Inject
  PaymentProcessor paymentProcessor;

  public void buyStuff()
  {
    paymentProcessor.processPayment(100);
  }
}

Red Hat logoGithubRedditYoutubeTwitter

詳細情報

試用、購入および販売

コミュニティー

Red Hat ドキュメントについて

Red Hat をお使いのお客様が、信頼できるコンテンツが含まれている製品やサービスを活用することで、イノベーションを行い、目標を達成できるようにします。

多様性を受け入れるオープンソースの強化

Red Hat では、コード、ドキュメント、Web プロパティーにおける配慮に欠ける用語の置き換えに取り組んでいます。このような変更は、段階的に実施される予定です。詳細情報: Red Hat ブログ.

会社概要

Red Hat は、企業がコアとなるデータセンターからネットワークエッジに至るまで、各種プラットフォームや環境全体で作業を簡素化できるように、強化されたソリューションを提供しています。

© 2024 Red Hat, Inc.