The Java EE 7 Tutorialの23 Introduction to Contexts and Dependency Injection for Java EEのセクションを読んでテキトーに訳した。
23 Introduction to Contexts and Dependency Injection for Java EE
Contexts and Dependency Injection for Java EE (CDI)はJava EEの機能の一つで、Web層とJava EEプラットフォームのトランザクション層とを結びつけるのに役立ちます。CDIを各種のサービスと一緒に使うことで、開発者はwebアプリケーションでJSFとEJBを簡単に使えるようになります。また、ステートフルオブジェクトと共に使えるよう設計されたCDIは多様な用途に利用でき、開発者に疎結合かつタイプセーフな方法で各種のコンポーネントを統合する優れた柔軟性を提供可能です。
CDI 1.1はJSR 346で仕様策定されています。CDIを使用する関連仕様は以下の通りです。
- JSR 330, Dependency Injection for Java
- Java EE 7 platform specificationから派生したManaged Beans仕様 (JSR 342)
以下のトピックをこのチュートリアルで扱います。
- Getting Started
- Overview of CDI
- About Beans
- About CDI Managed Beans
- Beans as Injectable Objects
- Using Qualifiers
- Injecting Beans
- Using Scopes
- Giving Beans EL Names
- Adding Setter and Getter Methods
- Using a Managed Bean in a Facelets Page
- Injecting Objects by Using Producer Methods
- Configuring a CDI Application
- Using the @PostConstruct and @PreDestroy Annotations with CDI Managed Bean Classes
- Further Information about CDI
23.1 Getting Started
Contexts and Dependency Injection (CDI)は、依存性を引数として渡したり生成する代わりに、ユーザのオブジェクトへ自動的に依存性を提供します。また、CDIは依存性のライフサイクルをユーザの代わりに管理します。
例えば、以下のようなservletを考えます。
@WebServlet("/cdiservlet") public class NewServlet extends HttpServlet { private Message message; @Override public void init() { message = new MessageB(); } @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write(message.get()); } }
このservletはMessage
インタフェースを実装するオブジェクトのインスタンスを必要とします。
public interface Message { public String get(); }
servletは以下のオブジェクトのインスタンスを生成します。
public class MessageB implements Message { public MessageB() { } @Override public String get() { return "message B"; } }
CDIを使用する場合、servletはMessage
インスタンスの依存性を宣言します。すると、CDIランタイムが自動的にインスタンスを注入します。servletのコードは以下のようになります。
@WebServlet("/cdiservlet") public class NewServlet extends HttpServlet { @Inject private Message message; @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { response.getWriter().write(message.get()); } }
CDIランタイムは、Message
インタフェースの実装クラスを探索し、MessageB
クラスを発見し、インスタンスを新規作成し、ランタイム上のservletにインスタンスを注入します。新規インスタンスのライフサイクルを管理するためには、CDIランタイムはインスタンスのスコープを把握する必要があります。この例では、servletはインスタンスをHTTPリクエストを処理する間でのみ必要とするため、それ以降インスタンスはGC可能になります。これを明示するにはjavax.enterprise.context.RequestScoped
アノテーションを使用します。
@RequestScoped public class MessageB implements Message { ... }
スコープの詳細についてはUsing Scopesを参照してください。
MessageB
クラスはCDI beanです。CDI beansとは、CDIがインスタンス化とマネージド化、および他オブジェクトの依存性を満たすために自動的に注入可能なクラス、のとこです。ほぼすべてのJavaクラスがCDIとして注入かマネージド化が可能です。beansの詳細についてはAbout Beansを参照してください。CDI beanを含むJAR/WARファイルはbean archiveです。bean archivesのパッケージングの詳細については、Configuring a CDI ApplicationとChapter 25, "Contexts and Dependency Injection for Java EE: Advanced Topics"のPackaging CDI Applicationsを参照してください。
このサンプルでは、MessageB
はMessage
インターフェースを実装するだけのクラスです。もしアプリケーションがインタフェースの実装を複数持つ場合、CDIは注入する実装を選択可能なメカニズムを備えています。詳細な情報については、Using QualifiersとUsing Alternatives in CDI Applications in Chapter 25, "Contexts and Dependency Injection for Java EE: Advanced Topics"を参照してください。
23.2 Overview of CDI
CDIが提供する最も基本的なサービスは以下の通りです。
- Contexts: このサービスは、事前定義(ライフサイクルコンテキストの拡張は可能)したステートフルコンポーネントとの相互作用とライフサイクルのバインドを行います。
- Dependency injection: このサービスは、コンポーネントをアプリケーションにタイプセーフな方法で注入し、また、デプロイ時に注入する個々のインターフェースの実装を選択して決定します。
くわえて、CDIは以下のサービスを提供します。
- Integration with the Expression Language (EL)は、JSFページやJSPページ内で任意のコンポーネントを直接使用できます。
- 注入されたコンポーネントをデコレートする機能。
- タイプセーフインターセプターバインディングを使用するコンポーネントにインターセプターを関連付ける機能。
- イベント通知モデル(An event-notification model)
- Java Servletが定義する三つの標準スコープ(request, session, application)プラスweb conversation scope.
- 完全なService Provider Interface (SPI)により、サードパーティフレームワークをJava EE 7環境に統合する手順が明確。
- サーバ実装を変更可能にするための、定義済みの型とqualifiersを使用したサーバ・クライアント間の疎結合化。
- 協調コンポーネントのライフサイクルの疎結合化を以下により行う。
- イベントを使用してconsumersからmessage producersを完全に疎結合にする。
- Java EEインターセプターを使用して直交する関心(orthogonal concerns)を疎結合にする。
- コンパイラーがタイプエラーを検出可能なように、ワイヤリングとコレーションに文字列ベースの名前を使用することでルックアップを削除。
- あらゆるものの指定に宣言的Javaアノテーションを使用することで、巨大なXMLデプロイメント記述子が不要になり、デプロイ時の依存構造の理解とコードを内部検査するツールを提供しやすくなります。
23.3 About Beans
CDIは、JavaBeansとEJBなどの他の技術で使われる以上にbeanの概念を再定義します。CDIでは、beanはcontextual objectsの元となります。contextual objectsはアプリケーションの状態もしくはロジックの両方を定義します。あるJava EEコンポーネントがbeanとなる条件は、CDI仕様が定義するライフサイクルコンテキストモデルに沿って、コンテナがそのインスタンスのライフサイクルを管理することです。
具体的には、beanは以下の属性を持ちます。
- bean typeの(空でない)組。
- qualifiers の(空でない)組。(Using Qualifiersを参照)
- スコープ。(Using Scopesを参照)
- オプションで、bean EL名(Giving Beans EL Namesを参照)
- インターセプタ・バインディングのセット。
- beanの実装。
bean typeは、クライアントから可視可能なbeanの型を定義します。おおよそすべてのJavaの型がbean typeになれます。
- bean typeは、final宣言が可能なインタフェース・実装クラス(concrete class)・抽象クラスで、finalメソッドを持つことが出来ます。
- bean typeは、型パラメータと型変数によりパラメータ化が可能です。
- bean typeは、配列型が可能です。要素型が同一の場合のみ、二つの配列型が同一と見なされます。
- bean typeは、プリミティブ型が可能です。
java.lang
のラッパー型が一致する場合にプリミティブ型が同一と見なされます。 - bean typeは、raw typeが可能です。
23.4 About CDI Managed Beans
managed beanは、beanクラスと呼ばれるJavaクラスによって実装されます。トップレベルのJavaクラスがmanaged beanとなる条件は、JSF仕様などその他のJava EE技術仕様がそのクラスをmanaged beanと定義するか、以下の状態をすべて満たす場合です。
- 非staticの内部クラスではない。
- 実装クラスか、
@Decorator
が付与されている。 - EJBコンポーネント定義のアノテーションが付与されていない、もしくは、
ejb-jar.xml
でEJBクラスとして宣言されていない。 - 適切なコンストラクタを持つ。以下のうちいずれかであること。
managed beanの定義には、アノテーションなどの特殊な宣言は必要ありません。
23.5 Beans as Injectable Objects
注入の考え方はここしばらくの間でJava技術の一部となっています。Java EE 5プラットフォーム登場以来、アノテーションがある種のオブジェクトとリソースをコンテナ管理オブジェクトに注入可能にしてきました。CDIは、より多くの種類のオブジェクトの注入と、それらのオブジェクトを非コンテナ管理オブジェクトに注入することを可能にします。
以下のオブジェクトが注入可能です。
- (ほぼすべての)Javaクラス。
- Session beans
- Java EE resources: data sources, Java Message Service topics, queues, connection factories
- Persistence contexts (JPAの
EntityManager
オブジェクト) - Producerフィールド。
- producerメソッドが返すオブジェクト。
- Web serviceの参照。
- リモートのEJB参照。
例えば、文字列を返すメソッドで単純なJavaクラスを作成したとします。
package greetings; public class Greeting { public String greet(String name) { return "Hello, " + name + "."; } }
このクラスはbeanとなり、別のクラスに注入可能となります。このbeanの形式ではELにはエクスポートされません。beanをELでアクセス可能にする方法についてはGiving Beans EL Namesを参照してください。
23.6 Using Qualifiers
qualifiersによって個々のbean typeで様々な実装を提供することが出来ます。qualifierはアノテーションでbeanに適用します。qualifier typeはJavaのアノテーションで@Target({METHOD, FIELD, PARAMETER, TYPE})
と@Retention(RUNTIME)
で定義します。
たとえば、@Informal
qualifier typeを宣言し、Greeting
の拡張クラスに適用可能です。このqualifier typeの宣言は以下のようなコードになります。
package greetings; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RUNTIME) @Target({TYPE, METHOD, FIELD, PARAMETER}) public @interface Informal {}
Greeting
を拡張するクラスにこのqualifierを使用します。
package greetings; @Informal public class InformalGreeting extends Greeting { public String greet(String name) { return "Hi, " + name + "!"; } }
アプリケーションでは両方の実装とも使用可能です。
qualifierなしでbeanを定義する場合、beanは自動的に@Default
qualifier を持ちます。アノテーションが付与されていないGreeting
クラスは以下のように宣言可能です。
package greetings; import javax.enterprise.inject.Default; @Default public class Greeting { public String greet(String name) { return "Hello, " + name + "."; } }
23.7 Injecting Beans
作成したbeanを使用するにはbeanを注入することで、JSFアプリケーションなどのアプリケーションで使用可能になります。例えば、Printer
と呼ばれるbeanを作成し、Greeting
beanの一つを注入します。
import javax.inject.Inject; public class Printer { @Inject Greeting greeting; ... }
このコードは@Default Greeting
実装をbeanに注入します。以下のコードは@Informal
実装を注入します。
import javax.inject.Inject; public class Printer { @Inject @Informal Greeting greeting; ... }
このbeanの全体像を理解するには他にも色々必要で、まずはスコープの使い方の理解が必要です。また、JSFアプリケーションでは、beanをEL経由でアクセス可能にする必要もあります。
23.8 Using Scopes
別のbeanクラスを注入するbeanを使用するwebアプリケーションでは、アプリケーションのユーザインタラクション中は状態を保持可能している必要がbeanにはあります。状態を定義するにはbeanにスコープを付与します。どういう使用方法をしたいかに応じて、Table 23-1に示す任意のスコープをオブジェクトに付与することが出来ます。
Table 23-1 Scopes
Scope | Annotation | Duration |
---|---|---|
Request | @RequestScoped |
webアプリケーションの単一HTTPリクエストのユーザインタラクション。 |
Session | @SessionScoped |
webアプリケーションの複数HTTPリクエストにまたがるユーザインタラクション。 |
Application | @ApplicationScoped |
webアプリケーションの全ユーザインタラクションで共有される状態。 |
Dependent | @Dependent |
未指定時のデフォルトスコープ。オブジェクトはクライアント(となるbean)に注入するために存在し、クライアント(のbean)と同じライフクサイクルとなります。 |
Conversation | @ConversationScoped |
JSFを含むservletのユーザインタラクション。conversationスコープは開発者定義の境界(developer-controlled boundaries)に位置付けられるスコープで、長時間実行conversationsの複数リクエストにまたがるスコープを拡張するものです。すべての長時間実行conversationは特定のHTTP servlet sessionスコープに関連付けられ、そのセッションの境界を超えることはありません。 |
最初の三つのスコープはJSF 346とJSF仕様で定義されています。最後の二つはJSR 346で定義されています。
@Dependent
を除くすべての定義済みスコープはcontextual scopeです。Java EE仕様が定義するライフサイクルコンテキストにcontextual scopeのbeanを配置します。たとえば、session contextとそのbeanはHTTPセッションのライフサイクルで存在します。beanに注入される参照はcontextを認識します(contextually aware)。参照を作成するスレッドのcontextにbeanは関連付けられ、参照は常にそのbeanに適用されます。CDIコンテナは、オブジェクトが生成されることと、指定されているスコープのタイミングに沿ってオブジェクトが注入されること、を保証します。
カスタムスコープの実装と定義も可能ですが、それは高度なトピックです。カスタムスコープはCDI仕様を拡張したり実装したりする開発者が使用するものです。
スコープは明確に定義されたライサイクルコンテキストをオブジェクトに付与します。scoped objectは必要な時に自動的に生成され、コンテキストが終端に達したら自動的に破棄されます。さらに、それらの状態は同一コンテキストで実行する任意のクライアントに自動的に共有されます。
servlet, EJB, JavaBeasnコンポーネントなどのJava EEコンポーネントは明確に定義されたスコープを持っていません。これらのコンポーネントは以下の一つとなります。
- EJB singleton beansなどのSingletonsの状態はすべてのクライアント間で共有されます。
- servletとstateless session beansなどのStatelessオブジェクトはクライアントから可視可能な状態は持ちません。
- JavaBeansコンポーネントとstateful session beansなどのクライアントは、クライアント間で参照を明示的に受け渡すことで状態を共有します。そのため、オブジェクトは明示的に生成と破棄をする必要があります。
もし、Java EEコンポーネントをmanaged beanとして生成する場合、明確に定義されたライフサイクルコンテキスト下で存在するscoped objectになります。
Printer
beanのwebアプリケーションは単純なリクエスト・レスポンスを使用するのでmanaged beanは以下のようなアノテーションを付与します。
import javax.enterprise.context.RequestScoped; import javax.inject.Inject; @RequestScoped public class Printer { @Inject @Informal Greeting greeting; ... }
session, application, conversation scopeを使用するbeanはserializableである必要があり、request scopeを使用するbeanはserializableでは無い必要があります。
23.9 Giving Beans EL Names
beanをELでアクセス可能にするには、組み込みqualifier@Named
を使います。
import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; @Named @RequestScoped public class Printer { @Inject @Informal Greeting greeting; ... }
@Named
qualifierは最初が小文字のbean名でbeanにアクセスできるようにします。たとえば、Faceletsページではprinter
でbeanを参照できます。
デフォルトでない名前を使うには@Named
qualifierに引数を指定します。
@Named("MyPrinter")
このアノテーションにより、FaceletsページはMyPrinter
でbeanを参照できます。
23.10 Adding Setter and Getter Methods
managed beanの状態をアクセス可能にするには、状態にsetter/getterメソッドを追加します。createSalutation
メソッドはbeanのgreet
メソッドを呼び出し、getSalutation
メソッドは結果を返します。
setter/getterメソッドを追加してbeanは完成です。最終的なコードは以下のようになります。
package greetings; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; @Named @RequestScoped public class Printer { @Inject @Informal Greeting greeting; private String name; private String salutation; public void createSalutation() { this.salutation = greeting.greet(name); } public String getSalutation() { return salutation; } public void setName(String name) { this.name = name; } public String getName() { return name; } }
23.11 Using a Managed Bean in a Facelets Page
Facelets pageでmanaged beanを使用するには、通常、メソッドの呼び出しと結果表示を行うUI要素のformを作成します。この例では、ユーザーに名前をタイプしてもらうボタンが押されると、挨拶文を取得し、ボタン下にテキストを表示します。
<h:form id="greetme"> <p><h:outputLabel value="Enter your name: " for="name"/> <h:inputText id="name" value="#{printer.name}"/></p> <p><h:commandButton value="Say Hello" action="#{printer.createSalutation}"/></p> <p><h:outputText value="#{printer.salutation}"/></p> </h:form>
23.12 Injecting Objects by Using Producer Methods
Producerメソッドが提供するのは、beanではないオブジェクト・実行時に値が変化する値を持つオブジェクト・カスタムの初期化を要求するオブジェクト、を注入する方法です。たとえば、数値の変数を@MaxNumber
qualifierで定義された値で初期化したいとすると、managed beanで変数を定義してproducerメソッドgetMaxNumber
をその変数用に定義します。
private int maxNumber = 100; ... @Produces @MaxNumber int getMaxNumber() { return maxNumber; }
managed beanにオブジェクトを注入するとき、コンテナは自動的にproducerメソッドを呼び出し、100で初期化します。
@Inject @MaxNumber private int maxNumber;
もし値が実行時に変化する場合、方法は若干異なります。たとえば、以下のコードは@Random
qualifierで定義されるランダム値を生成するproducerメソッドを定義しています。
private java.util.Random random = new java.util.Random( System.currentTimeMillis() ); java.util.Random getRandom() { return random; } @Produces @Random int next() { return getRandom().nextInt(maxNumber); }
managed beanにオブジェクトを注入するとき、オブジェクトのcontextual instanceを宣言します。
@Inject @Random Instance<Integer> randomInt;
それからInstance
のget
メソッドを呼び出します。
this.number = randomInt.get();
23.13 Configuring a CDI Application
beanにスコープのアノテーションを付与されている場合、サーバはアプリケーションがbean archiveかつ追加の設定は不要であると認識します。CDI beanが取りうるスコープはUsing Scopesに一覧があります。
CDIはオプションでデプロイメント記述子beans.xml
を使用できます。他のJava EEのデプロイメント記述子と同様に、beans.xml
の設定はCDIクラスのアノテーション設定に追加されます。beans.xml
の設定は、衝突しなければ、アノテーション設定を上書きします。archiveは、特定の限定された状況下でのみ、beans.xml
デプロイメント記述子を持つ必要があります。詳細はChapter 25, "Contexts and Dependency Injection for Java EE: Advanced Topics"を参照してください。
webアプリケーションでは、beans.xml
がもし必要であれば、WEB-INF
に配置しなければなりません。EJBモジュールやJARファイルでは、beans.xml
がもし必要であれば、META-INF
に配置しなければなりません。
23.14 Using the @PostConstruct and @PreDestroy Annotations with CDI Managed Bean Classes
CDIのmanaged beanクラスとそのスーパークラスは初期化用とbean破棄用のアノテーションをサポートしています。これらのアノテーションはJSR 250( Common Annotations for the Java platform(https://jcp.org/en/jsr/detail?id=250))で定義されています。
23.14.1 To Initialize a Managed Bean Using the @PostConstruct Annotation
ライフサイクルコールバックメソッドが定義されているmanaged beanを初期化すると、CDIフレームワークはDI実行後かつクラスがサービスに入れられる前に、そのメソッドを呼び出します。
managed beanがコンポーネントに注入されるとき、CDIはすべての注入終了後かつ全イニシャライザが呼び出された後に、そのメソッドを呼び出します。
Note:JSR 250に記述されているように、もしアノテーション付与されたメソッドがスーパークラスに宣言されている場合、サブクラスがそのメソッドをオーバーライドしない限り呼び出されます。
The guessnumber-cdi CDI ExampleのUserNumberBean
managed beanはすべてのフィールドをリセットするメソッドに@PostConstruct
アノテーションを使用しています。
@PostConstruct public void reset () { this.minimum = 0; this.userNumber = 0; this.remainingGuesses = 0; this.maximum = maxNumber; this.number = randomInt.get(); }
23.14.2 To Prepare for the Destruction of a Managed Bean Using the @PreDestroy Annotation
managed beanの破棄準備(Preparing for the destruction)が指定するライフサイクルコールバックメソッドは、アプリケーションコンポーネントがコンテナによって破棄されようとしていることを通知する方法です。
- managed beanクラスやそのスーパークラスで、managed beanの破棄時に呼ばれるメソッドを作成します。
そのメソッドで、beanが破棄される前に、beanが保持しているリソースの解放などの、必要な任意のクリーンナップ処理を実行します。 javax.annotation.PreDestroy
アノテーションをメソッドに付与します。
23.15 Further Information about CDI
CDIに関するより詳細な情報については下記を参照してください。
- Contexts and Dependency Injection for Java EE specification:
http://jcp.org/en/jsr/detail?id=346 - An introduction to Contexts and Dependency Injection for Java EE:
http://docs.jboss.org/weld/reference/latest/en-US/html/ - Dependency Injection for Java specification:
http://jcp.org/en/jsr/detail?id=330 - Managed Beans specification, which is part of the Java Platform, Enterprise Edition 7 (Java EE 7) Specification:
http://jcp.org/en/jsr/detail?id=342
関連リンク
- The Java EE 7 Tutorialのテキトー翻訳まとめ - Qiita - Java EE 7 Tutorialのうち、自分がテキトー翻訳したものの一覧