まさかの時のCDIポータブル・エクステンション

この記事はCDI 2.0スペックリードであるAntoine Sabot-Durand氏の記事(Feb 6, 2017)の翻訳です。CDIコンテナーのライフサイクルとイベントについてフローチャートを使って内部動作をわかりやすく説明しています。エクステンションについてこれだけ詳細に解説した記事は他にはないと思います。

(原文)Nobody expects the CDI portable extensions


rainbow lego

ポータブル・エクステンションはCDIの中でおそらく最もクールな機能です。

残念なことに、この貴重な機能は仕様書の隠れたところにあって、それにまったく気づかない開発者がいますし、エクステンションを使うのは複雑過ぎると考える人もいます。

この記事では、誰でも自分の仕事で簡単な機能や先進的なインテグレーション機能を提供するためにエクステンションを使えるということを示します。

でも、まず最初に「なぜポータブル・エクステンションを開発する必要があるのか」という基本的な質問に答えましょう。

エクステンションを使って何ができるのか

CDIは構成情報とBeanグラフを生成するためにブート時にクラスパス内のほとんどのクラスをスキャンします。この瞬間に生成されるその構成情報とメタデータは静的な内容(クラスファイル)であり、もっと動的な内容が必要になることがあります。

ここがポータブル・エクステンションが役に立つところになります。

CDIポータブル・エクステンションは、ブート時に発生するCDIスキャンプロセスにフックし、CIコンテナーによって生成されるメタデータを修正したり情報を追加したりすることを可能にします。

それはBeanを追加したり、Beanになるべき型の集合からクラスを削除したり、CDIに存在するプロデューサーやオブザーバー、SPI elements のほとんどを追加したりすることを含みます。

要約すると、エクステンションとは、開発者がCDIを構成やクラスを読んで生成されるデフォルトの振る舞いを上書きすることを可能にする手段になります。

CDIエクステンションのはじめに

 CDIポータブル・エクステンションはJava SE service-providerに基づいています。

サービスインタフェースjavax.enterprise.inject.spi.Extensionで、エクステンションを追加するためにjavax.enterprise.inject.spi.Extensionインタフェースを実装し、このクラスの完全修飾名をMETA-INF/services/javax.enterprise.inject.spi.Extensionサービスプロバイダーのテキストファイルに追加する必要があります。

エクステンションの機能は、CDIコンテナーの特定のライフサイクルイベントに対するオブザーバーを追加することによって定義されます。ブート時に、CDIコンテナーは、すべてのエクステンションを発見してこれらのオブザーバーを登録するために、サービスプロバイダーのメカニズムを使います。

このアプローチはコンテナーの内部ライフサイクルのステップにフックしてそれらの結果を変更することを可能にします。

では、これらのステップをチェックしましょう。

エクステンションのステップ

エクステンションがどのように動作するかを理解するため、コンテナーのライフサイクルを4つの大きなステップに分割するところから始めましょう。

broaderlifecycle-jp.png

CDIコンテナーライフサイクルの主要なステップ

(「アプリケーション実行」以外の)各ステップは1つ以上のイベントを含み、各イベントに対してCDI要素の発見やメタデータの構築のために1つ以上のオブザーバーをエクステンション内で定義することができます。

これらの各ステップについて焦点を絞り、それぞれで使うことができるイベントを説明しましょう。

以下のサンプルのいくつかはCDI 2.0の新機能を使います。CDI 1.2でいかに同じ効果を得ることができるのかについて説明しましょう。

型を発見するフェーズ

型の発見はこのように図解することができます。

 

typediscovery-jp.png
型の発見

caution.png
この図(とこれ以降の図)では、黄色の箱はエクステンションがイベントを観察してアクションを実行する場所で、灰色の箱は内部でのコンテナーの振る舞いを簡単に示したものです。

このステップのゴールは、Beanの候補となるAnnotatedTypeの集合を作ることです。

この集合はBeforeTypeDiscoveryAfterDiscoveryオブザーバーによって明示的に追加することができます。

その集合には自動的にコンテナーによってクラスをスキャンする処理でも自動的に追加されます。その処理には、ProcessAnnotatedTypeオブザーバーを使って発見されたものを変更するために開発者がフックを置くことができます。

スキャン前の型の追加(BeforeBeanDiscoveryイベント)

CDIコンテナーがクラスパス上で型のスキャンを開始する前に、BeforeBeanDiscoveryイベントを発火します。

このイベントを観察することで、特定の型を発見された型の集合に追加したり、限定子、ステレオタイプやインターセプター・バインディングのような特定のCDIアノテーションを追加することが可能になります。

public interface BeforeBeanDiscovery {
  void addQualifier(Class<? extends Annotation> qualifier);❶ 
  void addQualifier(AnnotatedType<? extends Annotation> qualifier); ❶
  void addScope(Class<? extends Annotation> scopeType, boolean normal, boolean passivating); ❷
  void addStereotype(Class<? extends Annotation> stereotype, Annotation... stereotypeDef); ❸
  void addInterceptorBinding(AnnotatedType<? extends Annotation> bindingType); ❹
  void addInterceptorBinding(Class<? extends Annotation> bindingType, Annotation... bindingTypeDef); ❹
  void addAnnotatedType(AnnotatedType<? extends Annotation> type, String id); ❺

  /* New methods in CDI 2.0 */
  <T> AnnotatedTypeConfigurator<T> addAnnotatedType(Class<T> type, String id); ❺
  <T extends Annotation> AnnotatedTypeConfigurator<T> configureQualifier(Class<T> qualifier); ❶
  <T extends Annotation> AnnotatedTypeConfigurator<T> configureInterceptorBinding(Class<T> bt); ❹
}

AnnotationAnnotatedType、またはAnnotatedTypeConfiguratior(CDI 2.0の場合)を使って新しい限定子を追加する
❷ 新しいスコープのAnnotationを追加する
Annotationとそれが意味するAnnotationsコレクションを与えて新しいステレオタイプを定義する
Annotationとそのメタアノテーション、AnnotatedType、またはAnnotatedTypeConfigurator(CDI 2.0の場合)を使って新しいインターセプターバインディングを追加する
❺ カスタムAnnotatedTypeから、またはAnnotatedTypeConfigurator(CDI 2.0の場合)新しいAnnotatedTypeを追加する
次の例はこのイベントの使い方を説明します。
public class MetricsExtension implements Extension { ❶

    public void addMetricAsQual(@Observes BeforeBeanDiscovery bbd) { ❷
        bbd.addQualifier(Metric.class); ❸
    }
}

❶ エクステンションを定義する(クラスのFQDNをMETA-INF/services/javax.enterprise.inject.spi.Extensionテキストファイルに追加することも忘れずに)
BeforeBeanDiscoveryライフサイクルイベントのためのオブザーバー
❸ サードパーティーの非CDIフレームワークからのアノテーションを限定子として定義する

上の例はDropwizard Metrics CDI integration extensionの一部です。それはCDI限定子として標準アノテーション(@Metrics)を宣言します。

また、非CDIクラスをコンテナーから管理対象Beanとして発見されるように変換することも可能です。

public class MyLegacyFrameworkService { ❶

    private Configurator config;

    public MyLegacyFrameworkService(Configurator config) {
        this.config = config;
    }
}

...

public class LegacyIntegrationExtension implements Extension {

    public void addLegacyServiceAsBean(@Observes BeforeBeanDiscovery bbd) {
        bbd.addAnnotatedType(MyLegacyFrameworkService.class,MyLegacyFrameworkService.class.getName()) ❷
                .add(ApplicationScoped.Literal.INSTANCE) ❸
                .filterConstructors(c -> c.getParameters().size() == 1)
                .findFirst().get().add(InjectLiteral.INSTANCE); ❹
    }

❶ レガシーフレームワークからのクラス。そのコードを変更することなくCDIプログラミングモデルに統合したい。
❷ MyLegacyFrameworkServiceクラスに基づいてAnnotatedTypeConfigurator(CDI 2.0新機能)を使う
❸ AnnotatedTypeConfiguratorの上に@ApplicationScopedスコープを追加する
❹ ひとつのパラメーターを取る最初のコンストラクターを探し、そのパラメーターに@Injectを追加する

上の例はCDI 2.0からの新機能を使っています:
AnnotatedTypeConfiguratorBeforeBeanDiscoveryイベントのaddAnnotatedType()メソッドの1つによって返される。もしもあなたがCDI 1.1を使っているなら同じ事ができますが、より煩雑な方法で同じことをするのにあなた自身のAnnotatedTypeを実装しなければなりません。新しいAnnotatedTypeを構成するには、それにスコープを追加し、そのコンストラクタの一つに@Injectアノテーションを追加します。オブザーバーの呼び出しの最後では、コンテナーはこのconfiguratorから自動的に一致するAnnotatedTypeを構築し、それを発見された型の集合に追加します。

自動的な型スキャンの処理

この最初のイベントの後、コンテナーはアプリケーションクラスパス内で型を探す処理を開始します。

このスキャンの構成はクラスパス上の(jarやmoduleなどの)Beanアーカイブごとに異なるものにすることができます。

アプリケーションパス内の各jarは、beans.xmlを含む・含まないのどちらの構成も可能です。このbeans.xmlは、CDIコンテナーによって型を探索するためにBeanアーカイブをどのようにスキャンするかを定義します。

CDIはグローバルな構成ファイルを提供しないので、各Beanアーカイブ(他のアーカイブを内包するwarも対象として含む)ごとにdiscovery modeを定義しなければなりません。

次の3つのdiscovery modeが存在します。

  • none: このBeanアーカイブでは型は発見されない
  • annotated(デフォルトモード): 特定のアノテーション(bean defining annotation)を持つクラスだけが発見される
  • all: すべての型が発見される

discovery modeはBeanアーカイブのbeans.xmlファイルを解析することで推測されます。

表1 discovery modeは何か
beans.xml file state discovery mode

beans.xml が存在しない

annotated

空の beans.xml

all

beans.xml (CDI 1.0 xsdを使用)

all

beans.xml (CDI 1.1 xsdを使用)

bean-discovery-mode 属性の値

exclusion filtersを使って型の発見を細かく制御することもできます。
CDI 2.0ではJava SEを使うとき、beans.xmlファイルを含まないjarはデフォルトで無視されます。

ProcessAnnotatedTypeイベント

このスキャンのフェーズの後は、コンテナーはAnnotatedTypeを生成し、発見された型(アノテーションを除く)ごとにProcessAnnotatedTypeイベントを発火します。

public interface ProcessAnnotatedType<X> { ❶
    AnnotatedType<X> getAnnotatedType(); ❷
    void setAnnotatedType(AnnotatedType<X> type); ❸
    void veto(); ❹

    /* New in CDI 2.0 */
    AnnotatedTypeConfigurator configureAnnotatedType(); ❸
}

❶ このイベントは、ユーザーが与えられた元の型に基づくAnnotatedTypeだけを処理できるようにパラメーター化された型である
❷ 現在処理されているAnnotatedTypeを返す
❸ 処理されているAnnotatedTypeAnnotatedTypeインタフェースを実装すること、またはAnnotatedTypeConfigurator(CDI 2.0の新機能)で新しいものに置き換える
❹ 処理されているAnnotatedTypeを発見された型の集合から取り除く。この型はBeanになることはない

このイベントは既存の型の構成を上書きするのによく使われます。
例えば、以下の例はサードパーティライブラリのStandardService上にtransactionalアノテーションを追加します。

public class AddTranscationalToServiceExtension implements Extension {

    public void addTransactional(@Observes ProcessAnnotatedType<StandardService> pat) {❶ 
        pat.configureAnnotatedType().add(new AnnotationLiteral<Transactional>(){});
    }

❶ オブザーバーはStandardService型に基づいた任意のAnnotatedTypeだけがトリガーとなる

インタフェースを実装するか、指定されたアノテーションを持つ(@WithAnnotationsフィルターに感謝)ことで、型を拒否(veto)できます。

public class VetEntitiesExtension implements Extension {

    public void vetoEntities(@Observes @WithAnnotations(Entity.class) ProcessAnnotatedType<?> pat) { ❶
        pat.veto();
    }

❶ オブザーバーは@Entityアノテーションを持つ任意の型に基づくAnnotatedTypeがトリガーとなる
最後の例はアプリケーション内のすべてのJPAエンティティをCDI Beanとして扱わないようにするために拒否します。

AfterTypeDiscoveryイベント

このイベントは型を発見する処理を終了させます。

public interface AfterTypeDiscovery {
    List<Class<?>> getAlternatives(); ❶
    List<Class<?>> getInterceptors(); ❶
    List<Class<?>> getDecorators(); ❶
    void addAnnotatedType(AnnotatedType<?> type, String id); ❷

    /* New in CDI 2.0 */
    <T> AnnotatedTypeConfigurator<T> addAnnotatedType(Class<T> type, String id); ❷
}

❶ これらのメソッドはオルタナティブBean、インターセプター、デコレーターとして発見されたクラスのリストへのアクセスを可能にする。これらの棚卸しリストはここで必要となるすべてを調査したり、これらのリストは変更可能なので新しいクラスを追加したりすることも可能である。
BeforeBeanDiscoveryと同様にして、発見されたAnnotatedTypeの集合にカスタムのAnnotatedTypeを追加できる。

次のエクステンションは、もしもLastInterceptorクラスがインターセプターとして発見されたならば、これはすべての他のインターセプターの後に呼び出されます。

public class lastInteceptorExtension implements Extension {

public void lastInterceptorCheck (@Observes AfterTypeDiscovery atd) {
        List<Class<?>> interceptors = atd.getInterceptors();
        if(interceptors.indexOf(LastInterceptor.class) < interceptors.size()) {
            interceptors.remove(LastInterceptor.class);
            interceptors.add(LastInterceptor.class);
        }
    }
}

Beanを発見するフェーズ

このフェーズでは、発見された型がBeanになる資格があるかどうかを調べるために分析します。

もしもそれが該当するなら、今後そのBeanを修正できるように一連のイベントが発火されます。

もしもそのBeanがエクステンションによって拒否されなかったならば、コンテナーはプロデューサーとオブザーバーの処理を始めます。

このフェーズの最後では、エクステンションはAfterBeanDiscoveryイベントを使ってカスタムBeanやオブザーバーを登録する機会を得ます。

このフェーズは、AfterDeploymentValidationイベントを使ってコンテナーによってすべての要素の妥当性を検証して終了します。

次のスキーマはすべてのフェーズのステップを図解したものです。最初のうちは複雑に見えるかもしれませんが、この処理は理解するのはどちらかといえば簡単です。

 

beandiscovery-jp.png

Beanの発見

ProcessInjectionPointイベント

この処理の間に遭遇したインジェクションポイントごとに、コンテナーはProjessInjectionPointイベントを発火します。インジェクションポイントは、管理対象Bean、プロデューサーメソッド、オブザーバーメソッドのために発火されます。

public interface ProcessInjectionPoint<T, X> { ❶
    InjectionPoint getInjectionPoint(); ❷
    void setInjectionPoint(InjectionPoint injectionPoint); ❸
    void addDefinitionError(Throwable t); ❹

    /* New in CDI 2.0 */
    InjectionPointConfigurator configureInjectionPoint(); ❸
}

❶ イベントはパラメーター化された型で、オブザーバーが特定のクラスの型 T を対象にし、その型 T には型 X のインジェクションポイントを含む
❷ このイベントによって処理されたInjectionPointを返す
❸ カスタムInjectionPointを実装するか、InjectionPointConfigurator(CDI 2.0新機能)を使うことによって処理されたInjectionPointの置き換えを可能にする
❹ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする

エクステンションはこのイベントを複数の理由から観察することができます。たとえば、与えられた限定子のためのすべての型を収集し、これらのインジェクションポイントに一致するBeanを後から生成するために使用できます。

public class ConvertExtension implements Extension {

    Set<Type> convertTypes = new HashSet();

    public void captureConfigTypes(@Observes ProcessInjectionPoint<?, ?> pip) {
        InjectionPoint ip = pip.getInjectionPoint();
        if (ip.getQualifiers().contains(Convert.Literal.Instance)) {
            convertTypes.add(ip.getType());
        }
    }
}

上の例は@Convert限定子を持つアプリケーション内のすべてのインジェクションポイントのための型の集合を生成します。

後に、発見された型に一致するカスタムBeanを生成するためにこのコレクションを使うことができます。

ProcessInjectionTargetイベント

InjectionTargetは、一見、管理対象Beanではないように見えます。それは主に依存性注入メカニズムといくつかのコールバック機能を提供します。
このイベントはインジェクションをサポートするすべての要素のために発火します。

public interface ProcessInjectionTarget<X> { ❶
    public AnnotatedType<X> getAnnotatedType(); ❷
    public InjectionTarget<X> getInjectionTarget(); ❸
    public void setInjectionTarget(InjectionTarget<X> injectionTarget); ❹
    public void addDefinitionError(Throwable t); ❺
}

❶ そのイベントは、InjectionTargetの基底の型を指定するパラメーター化された型である。
❷ 処理されるInjectionTargetを定義していたAnnotatedTypeを返す
❸ このイベントによって処理されたInjectionTargetを返す
❹ 処理されるのInjectionTargetを置き換えられるようにする
❺ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする

このイベントを観測することでエクステンションがデフォルトのInjectionTargetの振舞いを上書きすることができ、サードパーティフレームワーク上の特定の機能を呼び出すなど、インジェクションの途中で特定のタスクを実行することができます。

ProcessBeanAttributesイベント

このイベントはコンテナー内で発見されたBeanを登録する前に発火されます。このイベントを観測することで属性を修正したり登録をキャンセルしたりすることができます。

このイベントは以下のすべての種類のBeanで発火されます。

  • 管理対象Bean
  • セッションBean
  • プロデューサーフィールド
  • プロデューサーメソッド
  • カスタムBean
public interface ProcessBeanAttributes<T> { ❶
    public Annotated getAnnotated(); ❷
    public BeanAttributes<T> getBeanAttributes(); ❸
    public void setBeanAttributes(BeanAttributes beanAttributes); ❹
    public void addDefinitionError(Throwable t); ❺
    public void veto(); ❻

    /* New in CDI 2.0 */
    public BeanAttributesConfigurator<T> configureBeanAttributes(); ❹
    public void ignoreFinalMethods(); ❼
}

❶ パラメーター化された型のイベントは、与えられた型に対してだけこのイベントを観測することができるようにする
❷ アノテーションが付加されたBeanを定義する型を返す(つまり、管理対象BeanやセッションBeanのためのAnnotatedType、プロデューサーのためのAnnotatedFieldまたはAnnotatedMethod、カスタムBeanのためのnull)
❸ 処理されるBeanAttributesを返す
BeanAttributesインタフェースを実装するか、BeanAttributesConfigurator(CDI 2.0新機能)を使うかのいずれかによって処理されるBeanAttributesを置き換えるようにする
❺ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする
❻ 一致するBeanを無視してその登録をスキップするようにコンテナーに要求する
❼ プロキシ生成に関して仕様書における制約を明示的にスキップするためのCDI 2.0の新しいメソッド

次のエクステンションは、開発者によって限定子が付いたSpecialClass型のBeanがひとつも追加されていないことをチェックします。なぜなら、それ用のカスタムBeanを登録するつもりがあるからです。(訳注:@Defaultは限定子が付いてないBeanに自動的に付加される)

public class CheckExtension implements Extension {

public void filterSpecialClassBean(@Observes ProcessBeanAttributes<SpecialClass> pba) {
        if(pba.getBeanAttributes().getQualifiers().contains(Default.Literal.INSTANCE))
            pba.veto();
    }
}

ProcessBeanイベント

このイベントはBeanがコンテナー内に登録されるときに発火されます。

public interface ProcessBean<X> { ❶ 
    public Annotated getAnnotated(); ❷
    public Bean<X> getBean(); ❸
    public void addDefinitionError(Throwable t); ❹
}
❶ より良いオブザーバーのフィルタリングのためのパラメーター化された型
❷ アノテーションが付加されたBeanを定義する型を返す(つまり、管理対象BeanやセッションBeanのためのAnnotatedType、プロデューサーのためのAnnotatedFieldまたはAnnotatedMethod、カスタムBeanのためのnull)
❸ 生成されたBeanを返す
❹ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする

このイベントは、主に指定されたBeanが生成されたことをチェックし、時には将来の使用のためにその定義を獲得します。ProcessBeanのオブザーバーはすべての種類のBeanを対象とします。もっと指定したい場合は、特定の種類のBeanのイベントだけを観測するためにこのイベントの子を使います。

processBean_hierarchy.png

ProcessProducerイベント

このイベントはアプリケーション内のすべてのプロデューサーに対して発火されます。プロデューサーは一種のBeanであることを思い出してください。でも、その定義と発見はそれを含むBeanに依存します。言いかえれば、Beanとして発見されなかったクラスで定義されたプロデューサーは無視されます。

それは主にコード生成の上書きを可能にします(つまり、エクステンションからアプリケーション内の特定のBeanインスタンスを生成するために書かれたコードを上書きできます)

public interface ProcessProducer<T, X> { 
    AnnotatedMember<T> getAnnotatedMember(); 
    Producer<T> getProducer(); 
    void addDefinitionError(Throwable t); 
    void setProducer(Producer<T> producer); 

    /* New in CDI 2.0 */
    ProducerConfigurator<X> configureProducer(); 
}
❶ より良いオブザーバーのフィルタリングのためのパラメーター化された型。T はそのプロデューサーを含むBeanのBeanクラスで、X はそのプロデューサーの型
❷ そのプロデューサーを定義するAnnotatedMemberを返す(つまり、フィールドプロデューサーにはAnnotatedFieldまたはメソッドプロデューサーにはAnnotatedMethod
❸ 処理されようとしているプロデューサーを返す
❹ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする
Producerインタフェースを実装するか、あるいはProducerConfiguratorヘルパー(CDI 2.0新機能)を使うかのいずれかによって処理されるプロデューサーを置き換えることを可能にする

次の例はMetrics-CDI extensionを参考にしたものです。ユーザーがアプリケーション内のメトリックのためのプロデューサーを宣言するとき、メトリックレジストリ内でそれがすでに存在するかどうかを調べます。もしも存在しているなら、新しいインスタンスを生成する代わりに、そのレジストリ内にあるものを返します。もしもそれが存在しなかったら、そのメトリックを生成するプロデューサーのコードを使用し、レジストリに追加し、アプリケーションに返します。

public class MetricsExtension implements Extension {

<T extends com.codahole.metrics.Metric> void processMetricProducer(@Observes ProcessProducer<?, T> pp, BeanManager bm) { ❶
        Metric m = pp.getAnnotatedMember().getAnnotation(Metric.class); ❷

        if (m != null) { ❸
            String name = m.name(); ❹
            Producer<T> prod = pp.getProducer(); ❺
            pp.configureProducer() ❻
                    .produceWith(ctx -> { ❼
                        MetricRegistry reg = bm.createInstance().select(MetricRegistry.class).get(); ❽
                        if (!reg.getMetrics().containsKey(name)) ❾
                            reg.register(name, prod.produce(ctx)); ➓
                        return (T) reg.getMetrics().get(name);⓫
                    });
        }
    }
}

❶ このオブザーバーはBeanManagerを必要とする。このヘルパーBeanはエクステンションにおける任意のオブザーバーに注入され得る
❷ プロデューサーに付加された@Metricアノテーションを取り出す
❸ もしもアノテーションが発見されなければ処理をスキップする
❹ アノテーションからメトリックの名前を取り出す
❺ コールバック処理で使えるように初期のプロデューサーを取得する
❻ 新しいProducerConfiguratorヘルパーを使う。CDI 1.2ではProducerの自分自身の実装を生成することもできる
❼ そのプロデューサーのインスタンスを生成するための関数コールバックを定義する
❽ レジストリBeanのインスタンスを取り出す
❾ 一致した名前のメトリックを探す
➓ もしも存在しなければオリジナルのプロデューサーコードを使って生成し、レジストリに格納する
⓫ レジストリから一致する名前のメトリックを返す

ProcessObserverMethodイベント

このイベントは有効化されたBean(Enabled Bean)で宣言されたすべてのオブザーバーに対して発火されます。
CDI 2.0以前は、それは主にオブザーバーメソッドの存在をチェックするためのイベントでした。CDI 2.0からは、これはObserverMethodの置き換えやそれを削除することが可能にすることでより制御ができるようになりました。

public interface ProcessObserverMethod<T, X> { 
    AnnotatedMethod<X> getAnnotatedMethod(); 
    ObserverMethod<T> getObserverMethod(); 
    void addDefinitionError(Throwable t); 

    /* new in CDI 2.0 */
    void setObserverMethod(ObserverMethod<T> observerMethod); 
    ObserverMethodConfigurator<T> configureObserverMethod(); 
    void veto(); 
}
❶ より良いオブザーバーフィルタリングのためのパラメータ化された型。Tはオブザーバーメソッドを含むBeanのBeanクラス、Xはそのイベントの型
ObserverMethodを定義するAnnotatedMethodを返す
ObserverMethodを返す
❹ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする
❺ カスタムObserverMethodインスタンスを提供するか、あるいはObserverMethodConfigurator(CDI 2.0新機能)を使うかのいずれかによってObserverMethodを削除するか、上書きすることを可能にする

以下の例はエクステンションがMyClassイベント型のすべての同期オブザーバーを非同期に切り替えることができます。

public class SwitchExtension implements Extension {

   public void switchToAsync(@Observes ProcessObserverMethod<?, MyClass> pom) {
       pom.configureObserverMethod().async(true);
   }
}

AfterBeanDiscoveryイベント

このイベントは、すべてのBean、プロデューサー、オブザーバーを発見した後で発火します。

それは発見されたメタデータを変更したり、改良したりする最後の機会になります。

public interface AfterBeanDiscovery {
    void addDefinitionError(Throwable t); ❶
    void addBean(Bean<?> bean); ❷
    void addObserverMethod(ObserverMethod<?> observerMethod); ❸
    void addContext(Context context); ❹
    <T> AnnotatedType<T> getAnnotatedType(Class<T> type, String id); ❺
    <T> Iterable<AnnotatedType<T>> getAnnotatedTypes(Class<T> type); ❻

    /* New in CDI 2.0 */
    <T> BeanConfigurator<T> addBean(); ❷
    <T> ObserverMethodConfigurator<T> addObserverMethod(); ❸
}
❶ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする
Beanインタフェースのカスタム実装を生成するか、あるいはBeanConfiguratorヘルパー(CDI 2.0新機能)を使うかのいずれかによってカスタムBeanを生成できるようになる
❸ カスタムObserverMethodインスタンスを提供するか、あるいはObserverMethodConfigurator(CDI 2.0新機能)を使うかのいずれかによってObserverMethodを削除するか、上書きすることを可能にする
❹ 新しいコンテキストをコンテナーに追加する
❺ 与えられたクラスとidを使って発見されたAnnotatedTypeを返す
❻ アプリケーション内のすべての発見されたAnnotatedTypeIterableを返す

 

AfterDeploymentValidationイベント

この最後のブートストラッピングイベントは、メタデータ内で期待されるすべてをチェックするためのフックです(オブザーバーはこれらのメタデーターを検査するためにBeanManagerを注入できることを思い出してください)。

このイベントが発火されるとき、コンテナー内のそのメタデータはもはやミュータブルではなく、アプリケーションは実行の準備ができています。

public interface AfterDeploymentValidation {
    void addDeploymentProblem(Throwable t); ❶
}

❶ 定義エラーを追加することでオブザーバーがデプロイメントを中止できるようにする

アプリケーションの生涯と死

ポータブルエクステンションの観点からは、そろそろ終わりに近づいています。

ブートストラッピングのたくさんのフェーズの後、アプリケーションはシャットダウンのイベントまで実行し続けます。それは最後のポータブルエクステンションイベントが発火されるときです。

BeforeShutdownイベント

このイベントはフックで、アプリケーションの生涯の間で生成された特定のリソースを掃除することができるようにするためのものです。

public interface BeforeShutdown {
}

おわりに

ポータブル・エクステンションはとてもパワフルなツールです。

それらを習得することは困難のように思えるかもしれませんが、一度、大半のSPIとこの記事で示したコンテナライフサイクルを理解すれば、アイディア次第で何でも作れる単なる大きなLEGOブロックの箱に過ぎないのです。