kagamihogeの日記

kagamihogeの日記です。

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

The Java EE 7 Tutorial25 Contexts and Dependency Injection for Java EE: Advanced Topicsのセクションを読んでテキトーに訳した。

25 Contexts and Dependency Injection for Java EE: Advanced Topics

このチャプターではContexts and Dependency Injection for Java EE (CDI)のより高度な機能について解説します。とくに、CDIの高度な機能が提供する強い型付けによる疎結合コンポーネントに触れます。

以下のトピックをここで扱います。

  • Packaging CDI Applications
  • Using Alternatives in CDI Applications
  • Using Producer Methods, Producer Fields, and Disposer Methods in CDI Applications
  • Using Predefined Beans in CDI Applications
  • Using Events in CDI Applications
  • Using Interceptors in CDI Applications
  • Using Decorators in CDI Applications
  • Using Stereotypes in CDI Applications

25.1 Packaging CDI Applications

Java EEアプリケーションをデプロイするとき、CDIはbean archvies内のbeanを探索します。bean archiveは任意のモジュールで、このモジュールにはCDIランタイムがmanageおよび注入が可能なbeanが含まれています。bean archivesには二種類あり、explicit bean archivesとimplicit bean archivesです。

explicit bean archivebeans.xmlデプロイメント記述子を含むarchiveで、このファイルは空ファイルが可能で、version number無し、もしくはversion number 1.1でbean-discovery-mode属性にallと設定します。たとえば、

<?xml version="1.0" encoding="UTF-8"?>
<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"
       version="1.1" bean-discovery-mode="all">
    ...
</beans>

CDIは、explicit archiveでは@Vetoedアノテーション付与されたものを除いて、任意のbeanをmanageおよび注入可能です。

implicit bean archiveは、scope typeアノテーションを付与されたbeanを含み、beans.xmlデプロイメント記述子を含まないか、bean-discovery-mode属性にannotatedを設定したbeans.xmlデプロイメント記述子を含みます。

implicit archiveでは、CDIはscope typeアノテーションを付与されたbeanのみmanageと注入が可能です。

webアプリケーションでは、beans.xmlデプロイメント記述子はもし存在するなら、WEB-INFディレクトリに配置します。EJBモジュールやJARファイルでは、beans.xmlデプロイメント記述子はもし存在するなら、META-INFディレクトリに配置します。

25.2 Using Alternatives in CDI Applications

あるbeanが異なる目的に使用するために複数バージョンを持つ場合、The simplegreeting CDI Exampleで示すように、あるqualifierか別のqualifierかをデプロイメント時に注入することでbeanを選択することが出来ます。

アプリケーションのソースコードを変更する代わりに、デプロイ時にalternativesを使うことが出来ます。

Alternativesは以下のような目的に良く使用されます。

  • 実行時に決定されるクライアント依存のビジネスロジックを扱うため。
  • 特定のデプロイメントシナリオを検証するbeanを指定するため(たとえば、国ごとの消費税法が要求する国ごとの消費税ビジネスロジック)。
  • テストに使用するbeanのダミー(モック)バージョンを作成するため。

beanをルックアップ、注入、EL解決で使用可能にするには、javax.enterprise.inject.Alternativeアノテーションを付与して、beans.xmlalternatives要素を指定します。

例えば、beanの完全なバージョンと、テストのためだけの簡易バージョンを作成したいとします。The encoder Example: Using Alternativesで解説されているサンプルは二つのbeanCoderImplTestCoderImplを持っています。テスト用のbeanは以下のアノテーションが付与されています。

@Alternative
public class TestCoderImpl implements Coder { ... }

完全なバージョンにはアノテーションはありません。

public class CoderImpl implements Coder { ... }

managed beanはCoderインターフェースのインスタンスを注入します。

@Inject
Coder coder;

アプリケーションがbeanのalternativeバージョンを使うのは、beans.xmlファイルで下記のようにバージョンが宣言されている場合だけです。

<beans ...>
    <alternatives>
        <class>javaeetutorial.encoder.TestCoderImpl</class>
    </alternatives>
</beans>

beans.xmlalternatives要素をコメントアウトするとCoderImplクラスが使われます。

同一インタフェースを実装する複数のbeanすべてに@Alternativeアノテーションを付与可能です。この場合、使用するalternative beanを記述したbeans.xmlファイルを指定しなければなりません。もしCoderImplにも@Alternativeアノテーションが付与されている場合、二つのbeanの一つを常にbeans.xmlに指定する必要があります。

beans.xmlファイルで指定されるalternativesは同一archiveのクラスにのみ適用されます。複数モジュールで構成されるアプリケーションでグローバルなalternativesを指定するには@Priorityアノテーションを使用します。以下がその例です。

@Alternative
@Priority(Interceptor.Priority.APPLICATION+10)
public class TestCoderImpl implements Coder { ... }

もし@Priorityアノテーションを付与された同一インタフェースを実装する複数alternative beansがある場合、高プライオリティ値のalternativeが選択されます。@Priorityアノテーションを使用する場合、beans.xmlalternativeを指定する必要はありません。

25.2.1 Using Specialization

Specializationは、あるbeanを別のbeanで代替できるという点でalternativesと似た機能を持ちます。しかし、すべての場合で他のbeanをオーバーライドしたい場合もあるでしょう。例として下記二つのbeanを定義したとします。

@Default @Asynchronous
public class AsynchronousService implements Service { ... }
@Alternative
public class MockAsynchronousService extends AsynchronousService { ... }

このとき、beans.xmlalternativeとしてMockAsynchronousServiceを宣言したとすると、以下の注入ポイントはMockAsynchronousServiceを解決します。

@Inject Service service;

しかし、以下はMockAsynchronousServiceではなくAsynchronousServiceを解決しますが、その理由はMockAsynchronousService@Asynchronous qualifierを持たないからです。

@Inject @Asynchronous Service service;

MockAsynchronousServiceが常に注入されることを保証するには、すべてのbean typeとAsynchronousServiceのbean qualifiersを実装する必要があります。しかし、もしAsynchronousServiceがproducer methodやobserver methodを宣言した場合、この扱いづらいメカニズムは他のbeanが決して呼ばれないことを保証しません*1。Specializationはシンプルなメカニズムを提供します。

Specializationは、実行時および開発時に動作します。もしあるbeanが別のbeanをspecializesすると宣言する場合、そのbeanはその他のbeanクラスを拡張し、実行時にspecialized beanが別のbeanを完全に置き換えます。もし前者のbeanがproducer methodで生成される場合、producer methodもオーバーライドする必要があります。

beanをspecializeするにはjavax.enterprise.inject.Specializesアノテーションを付与します。たとえば、以下のようにbeanを宣言します。

@Specializes
public class MockAsynchronousService extends AsynchronousService { ... }

この場合、MockAsynchronousServiceクラスがAsynchronousServiceの代わりに常に呼び出されます。

通常、@Specializesアノテーションが付与されたbeanはalternativeでありbeans.xmlalternativeとして宣言されます。そうしたbeanはデフォルト実装を置き換えることを意味し、alternative実装は自動的にデフォルト実装の全qualifiersを継承します。もしEL名があればそれも継承します。

25.3 Using Producer Methods, Producer Fields, and Disposer Methods in CDI Applications

producer methodは注入可能なオブジェクトを生成します。一般的に、以下の場合にproducer methodsを使用します。

  • そのbean自身ではないオブジェクトを注入するとき。
  • 注入されるオブジェクトの実装型が実行時に変化するとき。
  • beanのコンストラクタが実行しないカスタム初期化をオブジェクトが要求するとき。

producer methodsの詳細な情報についてはInjecting Objects by Using Producer Methodsを参照してください。

producer fieldはproducer methodのシンプルな別の方法で、オブジェクトを生成するbeanのフィールドです。単純なgetterメソッドの代わりに使用可能です。Producer fieldsは、データソース・JMSリソース・webサービス参照のようなJava EEリソースを宣言するのに役立ちます。

producer methodやfieldにはjavax.enterprise.inject.Producesアノテーションを付与します。

25.3.1 Using Producer Methods

producer methodによって、開発時とデプロイ時ではなく実行時にbean実装を選択可能になります。たとえば、The producermethods Example: Using a Producer Method to Choose a Bean Implementationでは、managed beanは以下のproducer methodを定義しています。

@Produces
@Chosen
@RequestScoped
public Coder getCoder() {

    switch (coderType) {
        case TEST:
            return new TestCoderImpl();
        case SHIFT:
            return new CoderImpl();
        default:
            return null;
    }
}

getCoderは実質的にはgetterメソッドで、メソッドと同じアノテーションとqualifierを付与することでcoderプロパティが注入され、インタフェースの選択したバージョンが使用されます。

@Inject
@Chosen
@RequestScoped
Coder coder;

qualifierの指定は必須で、注入するCoderCDIに伝えます。qualifier無しでは、CDI実装はCoderImpl, TestCoderImpl, getCoderが返すいずれかの型から一つを選ぶことが出来ず、曖昧な依存関係をユーザに知らせてデプロイをキャンセルします。

25.3.2 Using Producer Fields to Generate Resources

producer fieldのよくある使い方はJPAEntityManagerChapter 37, "Introduction to the Java Persistence API,"を参照)やJDBCDataSourceなどのオブジェクトを生成することです。こうしたオブジェクトはコンテナによってmanagedされます。たとえば、@UserDatabasequalifierを作成して以下のようにentity manager用のproducer fieldを宣言可能です。

@Produces
@UserDatabase
@PersistenceContext
private EntityManager em;

@UserDatabasequalifierが使用可能な場所は、別のbeanにオブジェクトを注入するとき、RequestBean、アプリケーションのどこででもOKです。

@Inject
@UserDatabase
EntityManager em;
...

The producerfields Example: Using Producer Fields to Generate Resourcesのサンプルではproducer fieldsでentity managerを生成する方法を示しています。似たような@Resource, @EJB, @WebServiceRefオブジェクトを注入するメカニズムも使用可能です。

resource injectionへの依存を最小化するために、アプリケーションの一か所でリソース用のproducer fieldを指定し、それからアプリケーションの必要な場所にオブジェクトを注入してください。

25.3.3 Using a Disposer Method

producer methodやproducer fieldをオブジェクト生成に使用し、必要であれば実行後に削除することが出来ます。そのためには、@Disposesアノテーションを付与したdisposer methodが必要です。たとえば、以下のようにentity managerをクローズ可能です。

public void close(@Disposes @UserDatabase EntityManager em) {
    em.close();
}

disposer methodはcontextが終了するときに自動的に呼ばれ(この例の場合、RequestBeanがconversation scopeなのでconversationの終了時)、closeメソッドの引数はproducer fieldが生成したオブジェクトになります。

25.4 Using Predefined Beans in CDI Applications

Java EEは以下のインタフェースを実装する定義済みbeanを提供します。

  • javax.transaction.UserTransaction: A Java Transaction API (JTA)ユーザトランザクション
  • java.security.Principal: 個人・企業・ログインIDなどの任意のエンティティを表現するプリンシパルの抽象的な概念です。注入されたプリンシパルにアクセスが行われるときは常に、プリンシパルは現在の呼び出し元のアイデンティティを表現しています。たとえば、プリンシパルは初期化時にフィールドに注入されます。その後、注入されたプリンシパルを使用するメソッドは、プリンシパルが注入されたオブジェクト上で呼び出されます。この例では、注入されたプリンシパルメソッドが実行されるときの現在の呼び出し元のアイデンティティを表現しています*2
  • javax.validation.Validator: beanインスタンス用のvalidator。このインタフェースを実装するbeanはdefault bean validation objectValidatorFactory用のValidatorオブジェクトを注入可能にします。
  • javax.validation.ValidatorFactory: 初期化されたValidatorインスタンスを返すためのファクトリークラス。このインタフェースを実装するbeanはdefault bean validation ValidatorFactory objectを注入可能にします。
  • javax.servlet.http.HttpServletRequest: クライアントからのHTTPリクエスト。このインタフェースを実装するbeanによってservletでリクエストの詳細を取得可能になります。
  • javax.servlet.http.HttpSession: クライアントとサーバ間のHTTPセッション。このインタフェースを実装するbeanによってservletでセッションに関する情報にアクセスし、セッションにオブジェクトをバインド可能になります。
  • javax.servlet.ServletContext: servletsがサーバブレットコンテナで通信するためのコンテキストオブジェクト。

定義済みbeanを注入するには、リソースにはjavax.annotation.ResourceアノテーションCDI beanにはjavax.inject.Injectアノテーションを使用して、beanのインスタンスを取得するための注入ポイントを作成します。bean typeごとに、beanが実装するインタフェースのクラス名を指定します。

Table 25-1 Injection of Predefined Beans

Predefined Bean Resource or CDI Bean Injection Example
UserTransaction Resource @Resource UserTransaction transaction;
Principal Resource @Resource Principal principal;
Validator Resource @Resource Validator validator;
ValidatorFactory Resource @Resource ValidatorFactory factory;
HttpServletRequest CDI bean @Inject HttpServletRequest req;
HttpSession CDI bean @Inject HttpSession session;
ServletContext CDI bean @Inject ServletContext context;

定義済みbeanは、dependentスコープおよび定義済みデフォルトqualifier@Defaultで注入されます。

注入されるリソースの詳細については、Resource Injectionを参照してください。

以下のコード片は定義済みbeanを注入するために@Resource@Injectアノテーションを使用する方法を示しています。このコードはユーザトランザクションとcontextオブジェクトをservletクラスTransactionServletに注入します。ユーザトランザクションjavax.transaction.UserTransactionインタフェースを実装する定義済みbeanのインスタンスです。contextオブジェクトはjavax.servlet.ServletContextインタフェースを実装する定義済みbeanのインスタンスです。

import javax.annotation.Resource;
import javax.inject.Inject;
import javax.servlet.http.HttpServlet;
import javax.transaction.UserTransaction;
...
public class TransactionServlet extends HttpServlet {
    @Resource UserTransaction transaction;
    @Inject ServletContext context;
    ...
}

25.5 Using Events in CDI Applications

イベントにより、コンパイル時の依存関係を必要としないbean間通信が可能になります。あるbeanがイベントを定義し、別のbeanがイベントを発火し、また別のbeanがイベントをハンドリングします。それぞれのbeanは別々のパッケージやアプリケーションの別階層で定義可能です。

25.5.1 Defining Events

イベントは以下の要素で構成されます。

  • Javaオブジェクトのイベントオブジェクト。
  • ゼロ個以上のevent qualifiers

例として、The billpayment Example: Using Events and InterceptorsサンプルのPaymentEventbeanは三つのプロパティとそのsetter/getterを持っています。

    public String paymentType;
    public BigDecimal value;
    public Date datetime;

    public PaymentEvent() {
    }

また、このサンプルは二種類のPaymentEventを区別するqualifiersを定義しています。すべてのイベントはデフォルトのqualifier @Anyも持ちます。

25.5.2 Using Observer Methods to Handle Events

イベントハンドラはイベントを受け取るためにobserver methodを使用します。

各observer methodは、引数として@Observesアノテーションと任意のqualifiersを付与された特定のイベントタイプを取ります。observer methodは、もしイベントオブジェクトがイベントタイプにマッチするか、イベントの全qualifiersがobserver methodのevent qualifiersとマッチする場合、イベント通知を受けます。

observer methodはイベント引数に加えて他の引数を取ることが出来ます。追加引数は注入ポイントと宣言可能なqualifiersです。

billpaymentサンプルのイベントハンドラPaymentHandlerは二つのobserver methodsを定義し、PaymentEventの各タイプごとに一つのメソッドがあります。

public void creditPayment(@Observes @Credit PaymentEvent event) {
    ...
}

public void debitPayment(@Observes @Debit PaymentEvent event) {
    ...
}

また、Observer methodsはconditionalかtransactionalにできます。

  • conditional observer methodは、observer methodを定義するbeanインスタンスがすでに現在のコンテキストに存在する場合のみ、イベント通知を受けます。conditional observer methodを宣言するには、@Observesの引数にnotifyObserver=IF_EXISTSを指定します。
@Observes(notifyObserver=IF_EXISTS)

デフォルトのunconditionalにするには、@Observes(notifyObserver=ALWAYS)を指定します。

  • transactional observer methodは、イベントが発火したトランザクションの完了前(before-completion)や完了後(after-completion)フェーズに、イベント通知を受けます。トランザクションが正常か非正常に完了した後にのみ通知が発生するようにも指定できます。transactional observer methodを作成するには、@Observesの引数に以下のいずれかを指定します。
@Observes(during=BEFORE_COMPLETION)

@Observes(during=AFTER_COMPLETION)

@Observes(during=AFTER_SUCCESS)

@Observes(during=AFTER_FAILURE)

デフォルトのnontransactionalを使用するには、@Observes(during=IN_PROGRESS)を指定します。

トランザクション完了前に呼び出されるobserver methodは、トランザクションインスタンストランザクションロールバックを強制するために、setRollbackOnlyメソッドを呼び出すことができます。

Observer methodsは例外をスロー可能です。もしtransactional observer methodが例外をスローすると、例外はコンテナによって捕捉されます。もしobserver methodがnontransactionalなら、例外はイベント処理を強制終了させ、他のobserver methodsは一切呼び出されません。

25.5.3 Firing Events

イベントを発火するには、javax.enterprise.event.Event.fireメソッドを呼びます。このメソッドはイベントを発火して任意のobserver methodsに通知を行います。

billpaymentサンプルでは、managed beanのPaymentBeanはユーザインタフェースから受け取る情報を使用して適切なイベントを発火します。4つのbeanがあり、二つはイベントオブジェクトで、二つはデータ保持用です。managed beanは二つのイベントbeanを注入します。payメソッドは、データ保持用オブジェクトを生成するためにnewを使用し、発火するイベントを選択するためにswitchを使います。

    @Inject
    @Credit
    Event<PaymentEvent> creditEvent;

    @Inject
    @Debit
    Event<PaymentEvent> debitEvent;

    private static final int DEBIT = 1;
    private static final int CREDIT = 2;
    private int paymentOption = DEBIT;
    ...

    @Logged
    public String pay() {
        ...
        switch (paymentOption) {
            case DEBIT:
                PaymentEvent debitPayload = new PaymentEvent();
                // populate payload ... 
                debitEvent.fire(debitPayload);
                break;
            case CREDIT:
                PaymentEvent creditPayload = new PaymentEvent();
                // populate payload ... 
                creditEvent.fire(creditPayload);
                break;
            default:
                logger.severe("Invalid payment option!");
        }
        ...
    }

fireメソッドの引数はデータ保持用クラスのPaymentEventです。発火されたイベントは、observer methodsが受け取ります。

25.6 Using Interceptors in CDI Applications

interceptorは、関連付けられたターゲットクラスで発生するライフサイクルイベントもしくはメソッド実行を割り込むために使われるクラスです。インターセプターが実行するタスクは、ログや監査など、アプリケーションのビジネスロジックから切り離され、また、アプリケーション内で繰り返し出現するものです。そうしたタスクはしばしばcross-cuttingタスクと呼ばれます。インターセプターはメンテナンスが容易になるように一箇所でそうしたタスクのコードを指定します。Java EEプラットフォームに初めてインターセプターが導入されたときはEJBを指定していました。Java EE 7プラットフォームにおいては、managed beanを含む全種類のJava EE managed objectをインターセプターに指定可能です。

Java EEのインターセプターについての詳細な情報は、Chapter 54, "Using Java EE Interceptors"を参照してください。

インターセプターのクラスはたいてい@AroundInvokeアノテーションを付与されたメソッドを含み、インターセプトされるメソッドが実行されるときにインターセプターが実行するタスクを指定します。また、ライフサイクルコールバックインターセプターを指定するために@PostConstruct, @PreDestroy, @PrePassivate, @PostActivateアノテーションを付与したメソッドを含むことも出来、@AroundTimeoutEJBタイムアウトインターセプターを指定することも出来ます。インターセプタークラスは一つ以上のインターセプターメソッドを含むことが出来ますが、各タイプに一つだけメソッドを作成しなければなりません。

インターセプターとともに、アプリケーションは一つ以上のinterceptor binding typesを定義し、これはターゲットbeanやメソッドにインターセプターを関連付けるアノテーションです。たとえば、billpaymentサンプルはinterceptor binding type named @Loggedとinterceptor named LoggedInterceptorがあります。interceptor binding typeはqualifier宣言に似た宣言ですが、javax.interceptor.InterceptorBindingアノテーションを使用します。

@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface Logged {
}

また、interceptor bindingにはjava.lang.annotation.Inheritedがあり、これはアノテーションスーパークラスから継承されることを意味します。さらに、@Inheritedアノテーションはカスタムスコープを適用します(このチュートリアルでは扱いません)が、qualifiersは適用しません。

interceptor binding typeは他のinterceptor bindingsを宣言可能です。

インターセプタークラスはinterceptor bindingと同様に@Interceptorアノテーションを使用します。例は、The billpayment Example: Using Events and Interceptorsを参照してください。

すべての@AroundInvokeメソッドjavax.interceptor.InvocationContext引数を取り、java.lang.Objectを返し、Exceptionをスローします。InvocationContextメソッドを呼び出し可能です。@AroundInvokeメソッドproceedメソッドを呼び出さなければならず、これはターゲットクラスのメソッド呼び出しを行います。

インターセプターとbinding typeを定義すると、beanの全メソッドか特定のメソッドでインターセプターを呼び出すことを指定するために、binding typeをbeanや個々のメソッドアノテーション付与が可能になります。たとえばbillpaymentサンプルでは、PaymentHandlerbeanには@Loggedアノテーションが付与されており、そのクラスの任意のビジネスメソッド呼び出しが、インターセプターの@AroundInvokeメソッド呼び出しを発生させることを意味しています。

@Logged
@SessionScoped
public class PaymentHandler implements Serializable {...}

しかし、PaymentBeanbeanでは、payresetメソッドのみが@Loggedアノテーションを付与されており、よってインターセプターはこれらのメソッドが呼び出されたときのみ呼び出されます。

@Logged
public String pay() {...}

@Logged
public void reset() {...}

CDIアプリケーションで呼び出されるインターセプターはbeans.xmlに指定します。たとえば、LoggedInterceptorクラスは以下のように指定されています。

<interceptors>
    <class>javaeetutorial.billpayment.interceptors.LoggedInterceptor</class>
</interceptors>

もしアプリケーションが一つ以上のインターセプターを使用する場合、インターセプターはbeans.xmlで指定された順に呼び出されます。

beans.xmlで指定するインターセプターは同一アーカイブのクラスにのみ適用されます。複数モジュールで構成されるアプリケーションでインターセプターをグローバルに指定するためには@Priorityを、以下のように使用します。

@Logged
@Interceptor
@Priority(Interceptor.Priority.APPLICATION)
public class LoggedInterceptor implements Serializable { ... }

低プライオリティのインターセプターが最初に呼び出されます。@Priorityアノテーションを使用する場合、beans.xmlにインターセプターを指定する必要はありません。

25.7 Using Decorators in CDI Applications

decoratorは、javax.decorator.Decoratorアノテーションを付与するクラスあるいはbeans.xmldecorators要素です。

デコレータbeanクラスはjavax.decorator.Delegateアノテーションを付与したデリゲート注入ポイント(delegate injection point)を持つ必要があります。この注入ポイントは、フィールド・コンストラクタ引数・デコレータクラスの初期化メソッド引数に設定可能です。

デコレータは外見的にはインターセプターに似ています。しかし、デコレータはインターセプターによって実行されるタスクと相互補完的なタスク実行を行います。インターセプターはメソッド実行とbeanのライフサイクルに関連付けられたcross-cuttingタスクを実行しますが、任意のビジネスロジックは実行不可能です。一方デコレータはbeanのビジネスメソッドインターセプトすることでビジネスロジックを実行します。インターセプターがそうであるように、異なる種類のアプリケーションで再利用可能にする代わりに、そのロジックは特定のアプリケーションに固有であることを意味します。

たとえば、encoderサンプルのalternative TestCoderImplクラスを使う代わりに、デコレータを以下のように作成可能です。

@Decorator
public abstract class CoderDecorator implements Coder {
    
    @Inject
    @Delegate
    @Any
    Coder coder;
    
    public String codeString(String s, int tval) {
        int len = s.length();

        return "\"" + s + "\" becomes " + "\"" + coder.codeString(s, tval)
                + "\", " + len + " characters in length";
    }
}

このデコレータを使用するサンプルはThe decorators Example: Decorating a Beanを参照してください。

このシンプルなデコレータは、CoderImpl.codeStringが返すエンコード済み文字列ではなく、詳細な出力を返します。より複雑なデコレータは他のビジネスロジックを実行したりデータベースの情報を格納したり出来ます。

デコレータは抽象クラスとして宣言可能で、その理由はインタフェースのすべてのビジネスロジックを実装する必要が無いからです。

CDIアプリケーションでデコレータをインターセプターやalternativeのように呼び出せるようにするには、beans.xmlに定義が必要です。たとえばCoderDecoratorクラスを以下のように定義します。

<decorators>
    <class>javaeetutorial.decorators.CoderDecorator</class>
</decorators>

アプリケーションが一つ以上のデコレータを使用する場合、デコレータはbeans.xmlで指定される順に呼び出されます。

アプリケーションがインターセプターとデコレータを両方とも持つ場合、最初にインターセプターが呼び出されます。つまり、デコレータはインターセプト出来ません。

beans.xmlで指定するデコレータは同一アーカイブのクラスにのみ適用されます。複数モジュールで構成されるアプリケーションでグローバルなデコレータを指定するには@Priorityアノテーションを、以下のサンプルのように使用します。

@Decorator
@Priority(Interceptor.Priority.APPLICATION)
public abstract class CoderDecorator implements Coder { ... }

低プライオリティのデコレータが最初に呼び出されます。@Priorityアノテーションを使用する場合、beans.xmlでデコレータを指定する必要はありません。

25.8 Using Stereotypes in CDI Applications

stereotypeとは、他のアノテーションを含む、beanに適用されるアノテーションの一種です。stereotypeは、似たような機能を実行する多数のbeanを有する、大規模アプリケーションで特に有用です。stereotypeは以下を指定するアノテーションの一種です。

  • デフォルトスコープ。
  • ゼロ個以上のinterceptor bindings。
  • オプションで、デフォルEL名を保証するための@Namedアノテーション
  • オプションで、このstereotypeがalternativeだとすべてのbeanに指定するための@Alternativeアノテーション

特定のstereotypeアノテーションが付与されたbeanは常に固有のアノテーションを使用するので、多数のbeanに同じアノテーションを適用する必要はありません。

たとえば、stereotypeのActionjavax.enterprise.inject.Stereotypeアノテーションを使用して作成したとします。

@RequestScoped
@Secure
@Transactional
@Named
@Stereotype
@Target(TYPE)
@Retention(RUNTIME)
public @interface Action {}

@Actionアノテーションを付与されたすべてのbeanはリクエストスコープ、デフォルトEL名、interceptor bindings@Transactional@Secureを持ちます。

また、stereotypeのMockを作成できます。

@Alternative
@Stereotype
@Target(TYPE)
@Retention(RUNTIME)
public @interface Mock {}

このアノテーションが付与されたすべてのbeanはalternativeになります。

同一beanに複数のstereotypeを付与可能で、以下のようなアノテーション付与されたbeanを作成可能です。

@Action
@Mock
public class MockLoginAction extends LoginAction { ... }

beanごとに異なるスコープを指定するために、stereotypeが指定するスコープをオーバーライドすることも可能です。以下の宣言はMockLoginActionbeanをリクエストスコープではなくセッションスコープに変えています。

@SessionScoped
@Action
@Mock
public class MockLoginAction extends LoginAction { ... }

CDIにはModelと呼ばれる組み込みstereotypeが利用可能で、これはMVCアプリケーションアーキテクチャのモデル層を定義するbeanで使うことを目的としています。このstereotypeは@Named@RequestScopedの両方を指定しています。

@Named
@RequestScoped
@Stereotype
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface Model {}

関連リンク

*1:would not ensure that the other bean was never invoked.の直訳なんだけど、実際のコード動かしてみないと良く分からん……

*2:セキュリティ関係は知識低いんで割とアバウトな訳になっている自信がある