kagamihogeの日記

kagamihogeの日記です。

The Java EE 7 TutorialのIntroduction to Contexts and Dependency Injection for Java EEの章をテキトーに訳した

The Java EE 7 Tutorial23 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アプリケーションでJSFEJBを簡単に使えるようになります。また、ステートフルオブジェクトと共に使えるよう設計された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());
    }
}

このservletMessageインタフェースを実装するオブジェクトのインスタンスを必要とします。

public interface Message {
    public String get();
}

servletは以下のオブジェクトのインスタンスを生成します。

public class MessageB implements Message {
    public MessageB() { }

    @Override
    public String get() {
        return "message B";
    }
}

CDIを使用する場合、servletMessageインスタンスの依存性を宣言します。すると、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 ApplicationChapter 25, "Contexts and Dependency Injection for Java EE: Advanced Topics"Packaging CDI Applicationsを参照してください。

このサンプルでは、MessageBMessageインターフェースを実装するだけのクラスです。もしアプリケーションがインタフェースの実装を複数持つ場合、CDIは注入する実装を選択可能なメカニズムを備えています。詳細な情報については、Using QualifiersUsing 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は以下のサービスを提供します。

CDIの主要テーマは疎結合です。CDIは以下を行います。

  • サーバ実装を変更可能にするための、定義済みの型とqualifiersを使用したサーバ・クライアント間の疎結合化。
  • 協調コンポーネントのライフサイクルの疎結合化を以下により行う。
    • 自動ライフサイクル管理によりコンポーネントをcontextualにする。
    • ステートフルコンポーネントを完全なメッセージパッシングサービスのようにインタラクト可能にする。
  • イベントを使用してconsumersからmessage producersを完全に疎結合にする。
  • Java EEインターセプターを使用して直交する関心(orthogonal concerns)を疎結合にする。

疎結合に加え、CDIは強い型付けを提供します。

  • コンパイラーがタイプエラーを検出可能なように、ワイヤリングとコレーションに文字列ベースの名前を使用することでルックアップを削除。
  • あらゆるものの指定に宣言的Javaアノテーションを使用することで、巨大なXMLデプロイメント記述子が不要になり、デプロイ時の依存構造の理解とコードを内部検査するツールを提供しやすくなります。

23.3 About Beans

CDIは、JavaBeansとEJBなどの他の技術で使われる以上にbeanの概念を再定義します。CDIでは、beanはcontextual objectsの元となります。contextual objectsはアプリケーションの状態もしくはロジックの両方を定義します。あるJava EEコンポーネントがbeanとなる条件は、CDI仕様が定義するライフサイクルコンテキストモデルに沿って、コンテナがそのインスタンスのライフサイクルを管理することです。

具体的には、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と定義するか、以下の状態をすべて満たす場合です。

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 (JPAEntityManagerオブジェクト)
  • 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)で定義します。

たとえば、@Informalqualifier 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は自動的に@Defaultqualifier を持ちます。アノテーションが付与されていない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を作成し、Greetingbeanの一つを注入します。

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になります。

Printerbeanの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;
    ...
}

@Namedqualifierは最初が小文字のbean名でbeanにアクセスできるようにします。たとえば、Faceletsページではprinterでbeanを参照できます。

デフォルトでない名前を使うには@Namedqualifierに引数を指定します。

@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ではないオブジェクト・実行時に値が変化する値を持つオブジェクト・カスタムの初期化を要求するオブジェクト、を注入する方法です。たとえば、数値の変数を@MaxNumberqualifierで定義された値で初期化したいとすると、managed beanで変数を定義してproducerメソッドgetMaxNumberをその変数用に定義します。

private int maxNumber = 100;
...
@Produces @MaxNumber int getMaxNumber() {
    return maxNumber;
}

managed beanにオブジェクトを注入するとき、コンテナは自動的にproducerメソッドを呼び出し、100で初期化します。

@Inject @MaxNumber private int maxNumber;

もし値が実行時に変化する場合、方法は若干異なります。たとえば、以下のコードは@Randomqualifierで定義されるランダム値を生成する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;

それからInstancegetメソッドを呼び出します。

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実行後かつクラスがサービスに入れられる前に、そのメソッドを呼び出します。

  1. managed beanクラスかそのスーパークラスで、初期化を実行するメソッドを定義する。
  2. javax.annotation.PostConstructアノテーションメソッドに宣言する。

managed beanがコンポーネントに注入されるとき、CDIはすべての注入終了後かつ全イニシャライザが呼び出された後に、そのメソッドを呼び出します。

Note:JSR 250に記述されているように、もしアノテーション付与されたメソッドスーパークラスに宣言されている場合、サブクラスがそのメソッドをオーバーライドしない限り呼び出されます。

The guessnumber-cdi CDI ExampleUserNumberBeanmanaged 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)が指定するライフサイクルコールバックメソッドは、アプリケーションコンポーネントがコンテナによって破棄されようとしていることを通知する方法です。

  1. managed beanクラスやそのスーパークラスで、managed beanの破棄時に呼ばれるメソッドを作成します。
    そのメソッドで、beanが破棄される前に、beanが保持しているリソースの解放などの、必要な任意のクリーンナップ処理を実行します。
  2. javax.annotation.PreDestroyアノテーションメソッドに付与します。

CDIはbeanの破棄開始前にこのメソッドを呼び出します。

23.15 Further Information about CDI

CDIに関するより詳細な情報については下記を参照してください。

関連リンク