第7章 コンテキストおよび依存関係の挿入 (CDI)
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 のリファレンス実装である Weld が含まれています。
CDI の利点
CDI には以下のような利点があります。
- 多くのコードをアノテーションに置き換えることにより、コードベースが単純化および削減されます。
- 柔軟であり、インジェクションおよびイベントを無効または有効にしたり、代替の Bean を使用したり、非 CDI オブジェクトを簡単にインジェクトしたりできます。
-
デフォルトとは異なる設定をカスタマイズする必要がある場合、任意で
beans.xml
ファイルをMETA-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: Contexts and Dependency Injection) を使用すると、アプリケーションの開発、コードの再利用、デプロイメント時または実行時のコードの調整、およびユニットテストを非常に柔軟に行うことができます。JBoss EAP には、CDI の参照実装である Weld が含まれます。これらのタスクは、エンタープライズアプリケーションで CDI を使用する方法を示しています。
Weld にはアプリケーション開発の特別なモードが含まれています。開発モードを有効にすると、CDI アプリケーションの開発を容易にする一部のビルドインツールが利用できます。
アプリケーションのパフォーマンスに悪影響を与えるため、開発モードは本番環境では使用しないでください。必ずデプロイメントモードを無効にしてから本番環境にデプロイしてください。
Web アプリケーションに対して開発モードを有効にするには、以下を行います。
Web アプリケーションの場合、サーブレット初期化パラメーター org.jboss.weld.development
を true
に設定します。
<web-app> <context-param> <param-name>org.jboss.weld.development</param-name> <param-value>true</param-value> </context-param> </web-app>
管理 CLI を使用して JBoss EAP に対して開発モードを有効にするには、以下を行います。
development-mode
属性を true
に設定すると、デプロイされたアプリケーションすべてに対して Weld 開発モードをグローバルに有効にすることが可能です。
/subsystem=weld:write-attribute(name=development-mode,value=true)
7.2.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 定義アノテーション
@Dependent public class BookShop extends Business implements Shop<Book> { ... }
他の JSR-330 実装との互換性を確保するために、@Dependent
を除くすべての pseudo-scope アノテーションは bean 定義アノテーションではありません。ただし、pseudo-scope アノテーションを含む stereotype アノテーションは bean 定義アノテーションです。
7.2.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>
という名前の子要素。この名前とこの値に対して定義されたシステムプロパティーはありません。
フィルターがアクティブな場合、タイプは検出から除外され、以下のいずれかの状態になります。
- 検出されるタイプの完全修飾名が、除外フィルターの名前属性の値に一致します。
- 検出されるタイプのパッケージ名が、除外フィルターの接尾辞 ".*" を含む名前属性の値に一致します。
- 検出されるタイプのパッケージ名が、除外フィルターの接尾辞 ".*" を含む名前属性の値で始まります。
例7.1 例: 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 コンポーネントに @Vetoed
アノテーションを付けて Java EE コンポーネントが Bean と見なされないようにすることができます。イベントは @Vetoed
アノテーションが付けられたタイプに対して実行されず、また @Vetoed
アノテーションが付けられたパッケージでは実行されません。詳細については「@Vetoed
」を参照してください。
7.2.3. インジェクションを使用した実装の拡張
インジェクションを使用して、既存のコードの機能を追加または変更できます。
この例では、既存のクラスに翻訳機能を追加します。メソッド buildPhrase
を持つ Welcome
クラスがすでにあることを前提とします。buildPhrase
メソッドは、都市の名前を引数として取得し、「Welcome to Boston!」などのフレーズを出力します。
この例では、想像上の Translator
オブジェクトが Welcome
クラスにインジェクトされます。Translator
オブジェクトは、文をある言語から別の言語に翻訳できる EJB ステートレス Bean または別のタイプの Bean になります。この例では、Translator
は挨拶全体を翻訳するために使用され、元の Welcome
クラスは変更されません。Translator
は、buildPhrase
メソッドが呼び出される前にインジェクトされます。
例: Translator bean の Welcome
クラスへのインジェクト
public class TranslatingWelcome extends Welcome { @Inject Translator translator; public String buildPhrase(String city) { return translator.translate("Welcome to " + city + "!"); } ... }
7.3. あいまいな依存関係または満たされていない依存関係
コンテナーが 1 つの Bean への注入を解決できない場合、依存関係があいまいとなります。
コンテナーがいずれの Bean に対しても注入の解決をできない場合、依存関係が満たされなくなります。
コンテナーは以下の手順を踏み、依存関係の解決をはかります。
- インジェクションポイントの Bean 型を実装する全 Bean にある修飾子アノテーションを解決します。
-
無効となっている Bean をフィルタリングします。無効な Bean とは、明示的に有効化されていない
@Alternative
Bean のことです。
依存関係があいまいな場合、あるいは満たされない場合は、コンテナーはデプロイメントを中断して例外を発生させます。
あいまいな依存関係を修正するには、「修飾子を使用したあいまいなインジェクションの解決」を参照してください。
7.3.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.3.2. 修飾子を使用したあいまいなインジェクションの解決
修飾子を使用してあいまいなインジェクションを解決できます。あいまいなインジェクションについては、あいまいな依存関係または満たされていない依存関係をお読みください。
以下の例はあいまいであり、Welcome
の 2 つの実装 (翻訳を行う 1 つと翻訳を行わないもう 1 つ) を含みます。翻訳を行う Welcome
を使用するには、インジェクションを指定する必要があります。
例: あいまいなインジェクション
public class Greeter { private Welcome welcome; @Inject void init(Welcome welcome) { this.welcome = welcome; } ... }
修飾子を使用したあいまいなインジェクションの解決
あいまいなインジェクションを解決するには、
@Translating
という名前の修飾子アノテーションを作成します。@Qualifier @Retention(RUNTIME) @Target({TYPE,METHOD,FIELD,PARAMETERS}) public @interface Translating{}
翻訳を行う
Welcome
に@Translating
アノテーションを付けます。@Translating public class TranslatingWelcome extends Welcome { @Inject Translator translator; public String buildPhrase(String city) { return translator.translate("Welcome to " + city + "!"); } ... }
インジェクションで翻訳を行う
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.4. 管理 Bean
Java EE では管理対象 Bean 仕様の共通定義が確立されています。管理対象 bean は、プログラミングの制限が最小限であるコンテナー管理オブジェクトとして定義され、POJO (Plain Old Java Object) として知られるようになりました。管理対象 bean はリソースのインジェクション、ライフサイクルコールバック、インターセプターなどの基本サービスの小さなセットをサポートします。EJBや CDI などのコンパニオン仕様は、この基本モデルに基づいて構築されます。
ごくわずかな例外を除き、パラメーターのないコンストラクター (または @Inject
アノテーションが指定されたコンストラクター) を持つ具象 Java クラスは bean になります。これには、すべての Java Bean と EJB セッション bean が含まれます。
7.4.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.4.2. CDI を用いたオブジェクトの Bean へのインジェクト
CDI コンポーネントがアプリケーションで検出されると、CDI は自動的にアクティベートされます。デフォルト値と異なるよう設定をカスタマイズする場合は、デプロイメントアーカイブに META-INF/beans.xml
ファイルまたは WEB-INF/beans.xml
ファイルを含めることができます。
他のオブジェクトにオブジェクトをインジェクトする
クラスのインスタンスを取得するには、bean 内でフィールドに
@Inject
アノテーションを付けます。public class TranslateController { @Inject TextTranslator textTranslator; ...
インジェクトしたオブジェクトのメソッドを直接使用します。
TextTranslator
にメソッドtranslate
があることを前提とします。// in TranslateController class public void translate() { translation = textTranslator.translate(inputText); }
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 ... }
Instance(<T>)
インターフェースを使用してインスタンスをプログラムにより取得します。Bean 型でパラメーター化されると、Instance
インターフェースはTextTranslator
のインスタンスを返すことができます。@Inject Instance<TextTranslator> textTranslatorInstance; ... public void translate() { textTranslatorInstance.get().translate(inputText); }
オブジェクトを Bean にインジェクトすると、Bean は全オブジェクトのメソッドとプロパティーを使用できるようになります。Bean のコンストラクターにインジェクトするときに、インジェクションがすでに存在するインスタンスを参照する場合以外は、Bean のコンストラクターが呼び出されるとインジェクトされたオブジェクトのインスタンスが作成されます。たとえば、セッションの存続期間内にセッションスコープの Bean をインジェクトしても、新しいインスタンスは作成されません。
7.5. コンテキストおよびスコープ
CDI では、特定のスコープに関連付けられた Bean のインスタンスを保持するストレージ領域をコンテキストと呼びます。
スコープは bean とコンテキスト間のリンクです。スコープとコンテキストの組み合わせは特定のライフサイクルを持つことができます。事前定義された複数のスコープが存在し、独自のスコープを作成できます。事前定義されたスコープの例は @RequestScoped
、@SessionScoped
、および @ConversationScope
です。
範囲 | 説明 |
---|---|
|
Bean は、参照を保持する Bean のライフサイクルにバインドされます。インジェクション Bean のデフォルトのスコープは |
|
Bean はアプリケーションのライフサイクルにバインドされます。 |
|
Bean はリクエストのライフサイクルにバインドされます。 |
|
Bean はセッションのライフサイクルにバインドされます。 |
|
Bean は会話のライフサイクルにバインドされます。会話スコープは、リクエストの長さとセッションの間であり、アプリケーションによって制御されます。 |
カスタムスコープ |
上記のコンテキストで対応できない場合は、カスタムスコープを定義できます。 |
7.6. 名前付き Bean
Bean には、@Named
アノテーションを使用して名前を付けることができます。Bean を命名することにより、Bean を Java Server Faces (JSF) と Expression Language (EL) で直接使用できるようになります。
@Named
アノテーションは、bean 名である任意のパラメーターを取ります。このパラメーターが省略された場合、デフォルトの bean 名は、最初の文字が小文字に変換された bean のクラス名になります。
7.6.1. 名前付き Bean の使用
@Named Annotation を使用した Bean 名の設定
@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
になります。JSF ビューで名前付き Bean を使用します。
<h:form> <h:commandButton value="Welcome visitors" action="#{greeter.welcomeVisitors}"/> </h:form>
7.7. Bean ライフサイクル
このタスクは、リクエストの残存期間の間 Bean を保存する方法を示しています。
インジェクトされた bean のデフォルトのスコープは @Dependent
です。つまり、bean のライフサイクルは、参照を保持する bean のライフサイクルに依存します。他の複数のスコープが存在し、独自のスコープを定義できます。詳細については、「コンテキストおよびスコープ」を参照してください。
Bean ライフサイクルの管理
必要なスコープで nean にアノテーションを付けます。
@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)); } }
Bean が JSF ビューで使用されると、Bean は状態を保持します。
<h:form> <h:inputText value="#{greeter.city}"/> <h:commandButton value="Welcome visitors" action="#{greeter.welcomeVisitors}"/> </h:form>
Bean は、指定するスコープに関連するコンテキストに保存され、スコープが適用される限り存続します。
7.7.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.8. 代替の 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.8.1. 代替を用いたインジェクションのオーバーライド
代替の Bean を使用すると、既存の Bean をオーバーライドできます。これらは、同じ役割を満たすクラスをプラグインする方法として考慮できますが、動作が異なります。代替の Bean はデフォルトで無効になります。
このタスクは、代替を指定し、有効にする方法を示しています。
インジェクションのオーバーライド
このタスクでは、プロジェクトに TranslatingWelcome
クラスがすでにあることを前提としています。ただし、これを "mock" TranslatingWelcome
クラスでオーバーライドするとします。これは、実際の Translator
Bean を使用できないテストデプロイメントのケースに該当します。
代替を定義します。
@Alternative @Translating public class MockTranslatingWelcome extends Welcome { public String buildPhrase(string city) { return "Bienvenue à " + city + "!"); } }
置換実装をアクティベートするために、完全修飾クラス名を
META-INF/beans.xml
またはWEB-INF/beans.xml
ファイルに追加します。<beans> <alternatives> <class>com.acme.MockTranslatingWelcome</class> </alternatives> </beans>
元の実装の代わりに代替実装が使用されます。
7.9. ステレオタイプ
多くのシステムでは、アーキテクチャーパターンを使用して繰り返し発生する Bean ロールのセットを生成します。ステレオタイプを使用すると、このようなロールを指定し、中心的な場所で、このロールを持つ Bean に対する共通メタデータを宣言できます。
ステレオタイプにより、以下のいずれかの組み合わせがカプセル化されます。
- デフォルトのスコープ。
- インターセプターバインディングのセット。
また、ステレオタイプにより、以下の 2 つのいずれかを指定できます。
- ステレオタイプがデフォルトの bean EL 名であるすべての bean。
- ステレオタイプが代替であるすべての bean。
Bean は、ステレオタイプを 0 個以上宣言できます。ステレオタイプは、他の複数のアノテーションをパッケージ化する @Stereotype
アノテーションです。ステレオタイプアノテーションは、bean クラス、プロデューサーメソッド、またはフィールドに適用できます。
ステレオタイプからスコープを継承するクラスは、そのステレオタイプをオーバーライドし、bean に直接スコープを指定できます。
また、ステレオタイプが @Named
アノテーションを持つ場合、配置された bean はデフォルトの bean 名を持ちます。この bean は、@Named
アノテーションが bean で直接指定された場合に、この名前をオーバーライドできます。名前付き bean の詳細については、「名前付き Bean」を参照してください。
7.9.1. ステレオタイプの使用
ステレオタイプを使用しないと、アノテーションが煩雑になる可能性があります。このタスクは、ステレオタイプを使用して煩雑さを軽減し、コードを効率化する方法を示しています。
例: 煩雑なアノテーション
@Secure @Transactional @RequestScoped @Named public class AccountManager { public boolean transfer(Account a, Account b) { ... } }
ステレオタイプの定義および使用
ステレオタイプを定義します。
@Secure @Transactional @RequestScoped @Named @Stereotype @Retention(RUNTIME) @Target(TYPE) public @interface BusinessComponent { ... }
ステレオタイプを使用します。
@BusinessComponent public class AccountManager { public boolean transfer(Account a, Account b) { ... } }
7.10. オブザーバーメソッド
オブザーバーメソッドは、イベント発生時に通知を受け取ります。
また、CDI は、イベントが発生したトランザクションの完了前または完了後フェーズ中にイベント通知を受け取るトランザクションオブザーバーメソッドを提供します。
7.10.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.10.2. トランザクションオブザーバー
トランザクションオブザーバーは、イベントが発生したトランザクションの完了フェーズ前または完了フェーズ後にイベント通知を受け取ります。トランザクションオブザーバーは、単一のアトミックトランザクションよりも状態が保持される期間が長いため、トランザクションオブザーバーはステートフルオブジェクトモデルで重要になります。
トラザクションオブザーバーには 5 つの種類があります。
-
IN_PROGRESS
: デフォルトではオブザーバーは即座に呼び出されます。 -
AFTER_SUCCESS
: トランザクションが正常に完了する場合のみ、オブザーバーはトランザクションの完了フェーズの後に呼び出されます。 -
AFTER_FAILURE
: トランザクションの完了に失敗する場合のみ、オブザーバーはトランザクションの完了フェーズの後に呼び出されます。 -
AFTER_COMPLETION
: オブザーバーはトランザクションの完了フェーズの後に呼び出されます。 -
BEFORE_COMPLETION
: オブザーバーはトランザクションの完了フェーズの前に呼び出されます。
以下のオブザーバーメソッドは、カテゴリーツリーを更新するトランザクションが正常に実行される場合のみアプリケーションコンテキストにキャッシュされたクエリー結果セットを更新します。
public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }
以下の例のように、JAP クエリーの結果セットをアプリケーションスコープでキャッシュしたと仮定します。
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.11. インターセプタ
インターセプターを使用すると、Bean のメソッドを直接変更せずに Bean のビジネスメソッドに機能を追加できます。インターセプターは、Bean のビジネスメソッドの前に実行されます。インターセプターは、JSR 318: Enterprise JavaBeans™ 3.1 仕様の一部として定義されています。
CDI により、インターセプターと Bean をバインドするアノテーションを利用できるため、この機能が強化されます。
インターセプションポイント
- ビジネスメソッドインターセプション: ビジネスメソッドのインターセプターは、Bean のクライアントによる Bean のメソッド呼び出しに適用されます。
- ライフサイクルコールバックインターセプション: ライフサイクルコールバックインターセプションは、コンテナーによるライフサイクルコールバックの呼び出しに適用されます。
- タイムアウトメソッドインターセプター: タイムアウトメソッドインターセプターは、コンテナーによる EJB タイムアウトメソッドの呼び出しに適用されます。
インターセプターの有効化
デフォルトでは、すべてのインターセプターが無効になります。インターセプターは、Bean アーカイブの beans.xml
記述子を使用して有効にすることができます。ただし、このアクティベーションは、そのアーカイブの Bean に対してのみ適用されます。CDI 1.1 以降、インターセプターは、@Priority
アノテーションを使用してアプリケーション全体に対して有効にできます。
例: 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"> <interceptors> <class>org.mycompany.myapp.TransactionInterceptor</class> </interceptors> </beans>
XML 宣言を使用すると、以下の 2 つのことが可能になります。
- システムでインターセプターの順序を指定して、結果が正確になるようにすることができます。
- デプロイメント時にインターセプタークラスを有効または無効にすることができます。
@Priority
を使用して有効にされたインターセプターは、beans.xml
ファイルを使用して有効にされたインターセプターよりも前に呼び出されます。
インターセプターが @Priority
によって有効になり、同時に beans.xml
ファイルによって呼び出されると、移植不可能な動作を引き起こします。したがって、複数の CDI 実装全体で整合性のある動作を維持するには、この組み合わせで有効にしないでください。
7.11.1. CDI とのインターセプターの使用
CDI により、インターセプターコードが単純化され、ビジネスコードへの適用が簡単になります。
CDI がない場合、インターセプターには 2 つの問題があります。
- Bean は、インターセプター実装を直接指定する必要があります。
- アプリケーションの各 Bean は、インターセプターの完全なセットを適切な順序で指定する必要があります。この場合、アプリケーション全体でインターセプターを追加または削除するには時間がかかり、エラーが発生する傾向があります。
CDI とインターセプターの使用
インターセプターバインディングタイプを定義します。
@InterceptorBinding @Retention(RUNTIME) @Target({TYPE, METHOD}) public @interface Secure {}
インターセプター実装をマーク付けします。
@Secure @Interceptor public class SecurityInterceptor { @AroundInvoke public Object aroundInvoke(InvocationContext ctx) throws Exception { // enforce security ... return ctx.proceed(); } }
開発環境でインターセプターを使用します。
@Secure public class AccountManager { public boolean transfer(Account a, Account b) { ... } }
META-INF/beans.xml
またはWEB-INF/beans.xml
ファイルに追加して、デプロイメントでインターセプターを有効にします。<beans> <interceptors> <class>com.acme.SecurityInterceptor</class> <class>com.acme.TransactionInterceptor</class> </interceptors> </beans>
インターセプターは、リストされた順序で適用されます。
7.12. デコレーター
デコレーターは、特定の 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.13. 移植可能な拡張機能
CDI は、フレームワーク、拡張機能、および他のテクノロジーとの統合の基礎となることを目的としています。したがって、CDI は、移植可能な CDI の拡張機能の開発者が使用する SPI のセットを公開します。
拡張機能は、以下のような種類の機能を提供できます。
- ビジネスプロセス管理エンジンとの統合。
- Spring、Seam、GWT、Wicket などのサードパーティーフレームワークとの統合。
- CDI プログラミングモデルに基づく新しいテクノロジー。
JSR-346 仕様に基づいて、移植可能な拡張機能は次の方法でコンテナーと統合できます。
- 独自の Bean、インターセプター、およびデコレーターをコンテナーに提供します。
- 依存関係注入サービスを使用した独自のオブジェクトへの依存関係のインジェクション。
- カスタムスコープのコンテキスト実装を提供します。
- アノテーションベースのメタデータを別のソースからのメタデータで拡大またはオーバーライドします。
For more information, see Portable extensions in the Weld documentation. You can also see the cdi-portable-extension
quickstart that ships with JBoss EAP for a working example of using CDI portable extension.
7.14. Bean プロキシー
通常、インジェクトされた bean のクライアントは bean インスタンスへの直接参照を保持しません。bean が依存オブジェクト (スコープ @Dependent
) でない場合、コンテナーはプロキシーオブジェクトを使用して、インジェクトされたすべての参照を bean にリダイレクトする必要があります。
この bean プロキシーはクライアントプロキシと呼ばれ、メソッド呼び出しを受け取る bean インスタンスが、現在のコンテキストと関連するインスタンスになるようにします。またクライアントプロキシは、他のインジェクトされた bean を再帰的にシリアライズせずに、セッションコンテキストなどのコンテキストにバインドされる bean をディスクへシリアライズできるようにします。
Java の制限により、コンテナーによるプロキシーの作成が不可能な Java の型があります。これらの型の 1 つで宣言されたインジェクションポイントが、@Dependent
以外のスコープを持つ bean に解決すると、コンテナーがデプロイメントをアボートします。
特定の Java の型ではコンテナーによってプロキシーを作成できません。これらの型には次のようなものがあります。
- パラメーターのない非プライベートコンストラクターを持たないクラス
-
final
が宣言されたクラスまたはfinal
メソッドを持つクラス。 - アレイおよびプリミティブ型。
7.15. インジェクションでのプロキシーの使用
各 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); } }