1. 概要

このチュートリアルでは、CDIポータブル拡張機能と呼ばれるCDI(コンテキストと依存性注入)の興味深い機能について説明します。

まず、それがどのように機能するかを理解することから始め、次に拡張機能を作成する方法を見ていきます。 Flyway用のCDI統合モジュールを実装する手順を実行して、CDIコンテナーの起動時にデータベースの移行を実行できるようにします。

このチュートリアルは、CDIの基本的な理解を前提としています。 CDIの概要については、この記事をご覧ください。

2. CDIポータブルエクステンションとは何ですか?

CDIポータブル拡張機能は、CDIコンテナの上に追加機能を実装できるメカニズムです。 ブートストラップ時に、CDIコンテナはクラスパスをスキャンし、検出されたクラスに関するメタデータを作成します。

このスキャンプロセス中に、CDIコンテナは、拡張機能でのみ監視できる多くの初期化イベントを発生させます。 ここで、CDIポータブル拡張機能が役立ちます。

CDI Portable拡張機能は、これらのイベントを監視し、コンテナーによって作成されたメタデータに情報を変更または追加します。

3. Mavenの依存関係

pom.xmlにCDIAPIに必要な依存関係を追加することから始めましょう。 空の拡張機能を実装するには十分です。

<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>2.0.SP1</version>
</dependency>

また、アプリケーションを実行するために、準拠したCDI実装を使用できます。 この記事では、Weldの実装を使用します。

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se-core</artifactId>
    <version>3.0.5.Final</version>
    <scope>runtime</scope>
</dependency>

APIおよび実装の新しいバージョンがMavenCentralでリリースされているかどうかを確認できます。

4. 非CDI環境でのFlywayの実行

Flyway とCDIの統合を開始する前に、まず、非CDIコンテキストで実行する方法を確認する必要があります。

それでは、Flyway 公式サイトから抜粋した次の例を見てみましょう:

DataSource dataSource = //...
Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.migrate();

ご覧のとおり、DataSourceインスタンスを必要とするFlywayインスタンスのみを使用しています。

CDIポータブル拡張機能は、後でFlywayおよびDatasourceBeanを生成します。 このサンプルでは、埋め込みH2データベースを使用し、DataSourceDefinitionアノテーションを介してDataSourceプロパティを提供します。

5. CDIコンテナ初期化イベント

アプリケーションのブートストラップで、CDIコンテナは、すべてのCDIポータブル拡張機能をロードしてインスタンス化することから始まります。 次に、各拡張機能で、初期化イベントのオブザーバーメソッドがあればそれを検索して登録します。 その後、次の手順を実行します。

  1. スキャンプロセスが開始する前にBeforeBeanDiscoveryイベントを発生させます
  2. アーカイブBeanをスキャンするタイプ検出を実行し、検出されたタイプごとにProcessAnnotatedTypeイベントを発生させます。
  3. AfterTypeDiscoveryイベントを発生させます
  4. Beanディスカバリーを実行します
  5. AfterBeanDiscoveryイベントを発生させます
  6. Bean Validationを実行し、定義エラーを検出します
  7. AfterDeploymentValidationイベントを発生させます

CDIポータブル拡張機能の目的は、これらのイベントを監視し、検出されたBeanに関するメタデータを確認し、このメタデータを変更するか、追加することです。

CDIポータブル拡張機能では、これらのイベントのみを監視できます。

6. CDIポータブル拡張機能の作成

独自のCDIポータブル拡張機能を構築することにより、これらのイベントのいくつかにフックする方法を見てみましょう。

6.1. SPIプロバイダーの実装

CDIポータブル拡張機能は、インターフェースjavax.enterprise.inject.spi.Extension。のJavaSPIプロバイダーです。JavaSPIの概要については、この記事を参照してください。

まず、Extensionの実装を提供することから始めます。 後で、オブザーバーメソッドをCDIコンテナのブートストラップイベントに追加します。

public class FlywayExtension implements Extension {
}

次に、次の内容のファイル名 META-INF / services /javax.enterprise.inject.spi.Extensionを追加します。

com.baeldung.cdi.extension.FlywayExtension

SPIとして、この Extension は、コンテナーのブートストラップの前にロードされます。 したがって、CDIブートストラップイベントのオブザーバーメソッドを登録できます。

6.2. 初期化イベントのオブザーバーメソッドの定義

この例では、スキャンプロセスを開始する前に、FlywayクラスをCDIコンテナに認識させます。 これは、 registerFlywayType()オブザーバーメソッドで実行されます。

public void registerFlywayType(
  @Observes BeforeBeanDiscovery bbdEvent) {
    bbdEvent.addAnnotatedType(
      Flyway.class, Flyway.class.getName());
}

ここでは、Flywayクラスに関するメタデータを追加しました。 これ以降、コンテナによってスキャンされたかのように動作します。この目的のために、 addAnnotatedType()メソッドを使用しました。

次に、 ProcessAnnotatedType イベントを観察して、FlywayクラスをCDI管理のbeanとして作成します。

public void processAnnotatedType(@Observes ProcessAnnotatedType<Flyway> patEvent) {
    patEvent.configureAnnotatedType()
      .add(ApplicationScoped.Literal.INSTANCE)
      .add(new AnnotationLiteral<FlywayType>() {})
      .filterMethods(annotatedMethod -> {
          return annotatedMethod.getParameters().size() == 1
            && annotatedMethod.getParameters().get(0).getBaseType()
              .equals(javax.sql.DataSource.class);
      }).findFirst().get().add(InjectLiteral.INSTANCE);
}

まず、Flywayクラスに@ApplicationScopedおよび@FlywayTypeアノテーションを付けてから、 Flyway.setDataSource(DataSource dataSource)を検索します。メソッドと@Inject。で注釈を付けます

上記の操作の最終結果は、コンテナが次のFlywayBeanをスキャンする場合と同じ効果があります。

@ApplicationScoped
@FlywayType
public class Flyway {
 
    //...
    @Inject
    public void setDataSource(DataSource dataSource) {
      //...
    }
}

次のステップは、FlywayBeanがDataSource Beanに依存しているため、DataSourceBeanをインジェクションに使用できるようにすることです。

そのために、 DataSource Beanをコンテナーに登録するプロセスを実行し、 After BeanDiscoveryイベントを使用します。

void afterBeanDiscovery(@Observes AfterBeanDiscovery abdEvent, BeanManager bm) {
    abdEvent.addBean()
      .types(javax.sql.DataSource.class, DataSource.class)
      .qualifiers(new AnnotationLiteral<Default>() {}, new AnnotationLiteral<Any>() {})
      .scope(ApplicationScoped.class)
      .name(DataSource.class.getName())
      .beanClass(DataSource.class)
      .createWith(creationalContext -> {
          DataSource instance = new DataSource();
          instance.setUrl(dataSourceDefinition.url());
          instance.setDriverClassName(dataSourceDefinition.className());
              return instance;
      });
}

ご覧のとおり、DataSourceプロパティを提供するDataSourceDefinitionが必要です。

管理対象Beanに次のアノテーションを付けることができます。

@DataSourceDefinition(
  name = "ds", 
  className = "org.h2.Driver", 
  url = "jdbc:h2:mem:testdb")

これらのプロパティを抽出するために、ProcessAnnotatedTypeイベントと@WithAnnotationsアノテーションを確認します。

public void detectDataSourceDefinition(
  @Observes @WithAnnotations(DataSourceDefinition.class) ProcessAnnotatedType<?> patEvent) {
    AnnotatedType at = patEvent.getAnnotatedType();
    dataSourceDefinition = at.getAnnotation(DataSourceDefinition.class);
}

最後に、 AfterDeployementValidation イベントをリッスンして、必要な Flyway BeanをCDIコンテナーから取得し、 mergerate()メソッドを呼び出します。

void runFlywayMigration(
  @Observes AfterDeploymentValidation adv, 
  BeanManager manager) {
    Flyway flyway = manager.createInstance()
      .select(Flyway.class, new AnnotationLiteral<FlywayType>() {}).get();
    flyway.migrate();
}

7. 結論

CDIポータブル拡張機能の構築は最初は難しいように思われますが、コンテナー初期化ライフサイクルと拡張機能専用のSPIを理解すると、JakartaEE上にフレームワークを構築するために使用できる非常に強力なツールになります。

いつものように、この記事に示されているすべてのコードサンプルは、GitHubにあります。