- はじめに
- CDI Lite と CDI Full の仕様分割
- Build Compatible Extensions
- bean-discovery-mode 属性のデフォルトが annotated となった
- Startup イベントと Shutdown イベント
- Handle からBeanのメタデータを簡単に参照できるようになった
- ステレオタイプに @Priority で優先度を指定可能となった
- まとめ
はじめに
Jakarta EE10 で導入される CDI 4.0 (Jakarta Contexts and Dependency Injection 4.0) についてのまとめです。 参照実装は Weld 5 です。
CDI Lite と CDI Full の仕様分割
2020年3月に公開された CDI Lite の原案が、多くの議論の後、2021年5月のコミュニティ投票にて実現が決定されました。
CDI Lite は CDI Full のサブセットとなります。 現在の CDI は「ランタイム指向」となっており、実行時の Bean ディスカバリーなどがアプリケーション起動時間短縮の足かせとなります。
これに対して、Quarkus の CDI 実装である ArC は、CDI仕様を限定的にサポートし、ビルドタイムに多くの処理を行うことで、起動時間の短縮を行うようになっています。
これからのクラウドネイティブなニーズに答えるために、現在の包括的で大きな CDI 仕様を軽量化したものが CDI Lite となります。
仕様書上も、Part I - Core CDI の中に、CDI Lite と CDI Full で章分けされています。 仕様上で明示されてはいませんが、 CDI Full は 「ランタイム指向」のままであり、CDI Lite は「ビルドタイム指向」と区別しても良いでしょう。
CDI Lite では利用できない(CDI Full に限定される)機能は以下となります。
- デコレータ
@Interceptors
アノテーションによるインタセプタ- ただし
@InterceptorBinding
と@Interceptor
で定義したインタセプタは CDI Light でも動作する
- ただし
- スペシャライゼーション(
@Specializes
によるBeanの差し替え) - Bean の非活性化(スコープに応じたBeanの非活性化)
- 会話スコープ(Conversation Scope)
- ポータブル・エクステンション
- 現状のポータブル・エクステンションは、ビルド指向のフレームワークには不向きであり、CDI Light では、新しい拡張APIを利用する(Build Compatible Extensions)
Jar としては、今まで通りの単一 Jar に両APIが含まれる形で提供されます。
Build Compatible Extensions
CDI Lite の目標の一つは、可能な限りリフレクションを排除することにあります。
しかし現在の Portable Extensions でのメタモデル(AnnotatedType
など)はリフレクションの利用が前提となっています。
そのため、CDI Lite では、Build Compatible Extensions で新しいメタモデル(AnnotationInfo, ClassInfo, MethodInfo, FieldInfo など)が導入されました。
これらの新しいメタモデルは jakarta.enterprise:jakarta.enterprise.lang-model
APIアーティファクトで提供されます。
Javaモジュール名は jakarta.cdi.lang.model
です(他のCDIは jakarta.cdi
というモジュール名になります)。
Build Compatible Extensions は、META-INF/services
で宣言された
jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension
インターフェースを介して提供されます。
Build Compatible Extensions は、ビルド時など、アプリケーションが開始される前にBean検出を実行できるようにするため CDI イベントのオブザーバーメソッドを宣言しません。
拡張機能の実行フェーズには、対応した以下のアノテーションが定義されています。
- @Discovery:アプリケーションへのクラスの追加、カスタムのCDIメタアノテーション登録
- @Enhancement:アノテーションの変換
- @Registration:登録されたビーン及びオブザーバをオブザーブ
- @Synthesis:合成Beanとオブザーバーの登録
- @Validation:カスタム検証の実行
Build Compatible Extensions についての詳細は別記事で紹介します。
bean-discovery-mode 属性のデフォルトが annotated となった
CDI 4.0 の beans.xml のスキーマは https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd
で定義されます。
CDI 3.0 では bean-discovery-mode
は必須定義となっていました。
<xs:attribute name="bean-discovery-mode" use="required">
CDI 4.0 ではオプショナルとなり、デフォルトとして annotated
が選択されたものとして扱われます。
<xs:attribute name="bean-discovery-mode" use="optional" default="annotated">
annotated
で対象となる の定義は以前と変わらず、以下の通りです。
@ApplicationScoped
と@RequestScoped
アノテーション- その他全てのノーマルスコープ(
@NormalScope
)を持つアノテーション @Interceptor
アノテーション- 全てのステレオタイプ(
@Stereotype
)を持つアノテーション - @Dependent スコープアノテーション
Startup イベントと Shutdown イベント
CDI では、EJBの @Startup
に該当するものがありませんでした。
EJB ではアプリケーションの起動時に以下のように @Startup
と @PostConstruct
で処理を行うことができます。
@javax.ejb.Startup @javax.ejb.Singleton public class StartupService { @PostConstruct public void init() { // ... } }
CDI では、@Initialized(ApplicationScoped.class)
というイベントをオブザーブすることでこれを模倣することができました。
@ApplicationScoped public class ObservingBean { public void initAppScope(@Observes @Initialized(ApplicationScoped.class) Object init) { } }
しかし、このイベントは、コンテナの状態イベントではなく、コンテキストに関連するイベントです。 特に「ビルドタイム指向」で CDI を使った場合には、実行前にコンテキストを開始することができるため、このイベントでコンテナ初期化時の処理を行うことは意味を成さなくなります。
CDI 4.0 では、jakarta.enterprise.event.Startup
と jakarta.enterprise.event.Shutdown
イベントが追加され、コンテナ起動時と終了時のイベントを正確にリスンすることができるようになりました。
今後は以下のように Startup
と Shutdown
を使うことで、@javax.ejb.Startup
と同様な機能を利用できます。
@ApplicationScoped public class ObservingBean { public void startup(@Observes Startup startup) { } public void initAppScope(@Observes @Initialized(ApplicationScoped.class) Object init) { } public void shutdown(@Observes Shutdown shutdown) { } public void observeBeforeShutdown(@Observes @BeforeDestroyed(ApplicationScoped.class) Object event) { } }
Handle からBeanのメタデータを簡単に参照できるようになった
Instance<T>
に Handle<T>
を取得するメソッドが追加されました。
public interface Instance<T> extends Iterable<T>, Provider<T> { Instance<T> select(Annotation... qualifiers); <U extends T> Instance<U> select(Class<U> subtype, Annotation... qualifiers); <U extends T> Instance<U> select(TypeLiteral<U> subtype, Annotation... qualifiers); Stream<T> stream(); // Handle 取得メソッド Handle<T> getHandle(); Iterable<Handle<T>> handles(); Stream<Handle<T>> handlesStream(); }
この Handle<T>
からは、Bean<T>
が取得でき、Beanのメタデータを参照することができます。
interface Handle<T> extends AutoCloseable { /** * The contextual reference is obtained lazily, i.e. when first needed. */ T get(); /** * @return the bean metadata */ Bean<T> getBean(); /** * Destroy the contextual instance. */ void destroy(); /** * Delegates to {@link #destroy()}. */ @Override void close(); }
旧来は以下のようにコンテナから管理のBeanを取得していました。
container.instance().select(Foo.class).get();
または Instance
を以下のようにインジェクトすることもできます。
@Inject Instance<FooInterface> instance; public void exec() { instance.select(Foo.class).get(); }
しかし、多くの Bean の候補を繰り返し処理した場合、 @Dependent
な Bean のインスタンスが都度作成され、効率的ではありませんでした。
CDI 4.0 からはHandle<T>
を介することで、Bean<T>
を取得してメタデータを参照できると共に、コンテキストインスタンスの取得と、必要に応じて破棄することが可能となりました。
ステレオタイプに @Priority
で優先度を指定可能となった
ステレオタイプは、@Stereotype
を付けたアノテーションで、さまざまなアノテーションをまとめたアノテーションを定義できます。
たとえば、 @Named
と @RequestScoped
をまとめるために、@Stereotype
を付与して @Model
を定義しています。
@Named @RequestScoped @Documented @Stereotype @Target({ TYPE, METHOD, FIELD }) @Retention(RUNTIME) public @interface Model { }
この CDI ステレオタイプに @Priority
で優先順位を与えられるようになりました。
特に、テストで特定のモックやスタブに切り替える必要がある場合に重宝するでしょう。
まとめ
Jakarta CDI 4.0 の変更点について紹介しました。
利用者視点ではインパクトの大きな変更はありませんが、クラウドネイティブ向けの「ビルドタイム指向」な CDI Lite 仕様ができたことは、Eclipse Microprofile 仕様を含めて、転換点にとなる重要なリリースだと思います。 既に Quarkus の ArC で実証が先行していますしね。
その他、こちらのチケットにあるように deprecated だったものの削除も行われています。