第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:Contexts and Dependency Injection for Java™ EE 1.1 の参照実装である 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 は、JBoss EAP の中核的なテクノロジーの 1 つであり、デフォルトで有効になります。CDI は、設定ファイルの該当するセクションをコメントアウトまたは削除することにより無効になっている場合があります。有効にする必要がある場合は、以下の手順を実行します。
JBoss EAP での CDI の有効化
JBoss EAP を停止します。
JBoss EAP により実行中に設定ファイルが変更されるため、設定ファイルを直接編集する前にサーバーを停止します。
適切な設定ファイルを編集します。
スタンドアロンサーバーの場合は
EAP_HOME/standalone/configuration/standalone.xml
、管理対象ドメインの場合はEAP_HOME/domain/configuration/domain.xml
を編集します。CDI 拡張機能を追加します。
org.jboss.as.weld
拡張機能がコメントアウトされた場合は、コメント解除します。全体が削除された場合は、以下の行をファイルの</extensions>
タグのすぐ上に新しい行で追加することにより復元します。<extension module="org.jboss.as.weld"/>
CDI サブシステムを追加します。
weld
サブシステムがコメントアウトされた場合は、コメント解除します。全体が削除された場合は、以下の行を<profiles>
セクションの該当するプロファイルに追加することにより復元します。<subsystem xmlns="urn:jboss:domain:weld:3.0"/>
- 更新された設定で 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 に対しても注入の解決をできない場合、依存関係が満たされなくなります。
コンテナーは以下の手順を踏み、依存関係の解決をはかります。
- インジェクションポイントの Bean 型を実装する全 Bean にある修飾子アノテーションを解決します。
-
無効となっている 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; } ... }
修飾子を使用したあいまいなインジェクションの解決
あいまいなインジェクションを解決するには、
@Translating
という名前の修飾子アノテーションを作成します。@Qualifier @Retention(RUNTIME) @Target({TYPE,METHOD,FIELD,PARAMETERS}) public @interface Translating{}
@Translating
アノテーションを使用して、翻訳を行うWelcome
をアノテートします。@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.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
を含めることができます。
他のオブジェクトにオブジェクトをインジェクトする
クラスのインスタンスを取得するには、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.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
.
範囲 | 説明 |
---|---|
|
Bean は、参照を保持する Bean のライフサイクルにバインドされます。インジェクション Bean のデフォルトのスコープは |
|
Bean はアプリケーションのライフサイクルにバインドされます。 |
|
Bean はリクエストのライフサイクルにバインドされます。 |
|
Bean はセッションのライフサイクルにバインドされます。 |
|
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 名の設定
@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.8. Bean ライフサイクル
このタスクは、リクエストの残存期間の間 Bean を保存する方法を示しています。
インジェクトされた Bean のデフォルトのスコープは @Dependent
です。つまり、Bean のライフサイクルは、参照を保持する Bean のライフサイクルに依存します。他の複数のスコープが存在し、独自のスコープを定義できます。詳細については、コンテキストおよびスコープを参照してください。
Bean ライフサイクルの管理
必要なスコープで 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)); } }
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 を使用できないテストデプロイメントのケースに該当します。
代替を定義します。
@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.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) { ... } }
ステレオタイプの定義および使用
ステレオタイプを定義します。
@Secure @Transactional @RequestScoped @Named @Stereotype @Retention(RUNTIME) @Target(TYPE) public @interface BusinessComponent { ... }
ステレオタイプを使用します。
@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 とのインターセプターの使用
インターセプターバインディングタイプを定義します。
@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.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); } }