CDIポータブルエクステンションとFlyway

1概要

このチュートリアルでは、CDIポータブルエクステンションと呼ばれるCDI(Context and Dependency Injection)の興味深い機能について説明します。

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

このチュートリアルはCDIの基本的な知識を前提としています。 CDIの概要については、https://www.baeldung.com/java-ee-cdi[この記事]をご覧ください。

2. CDI Portable Extensionとは何ですか?

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

  • このスキャンプロセスの間に、CDIコンテナは多くの初期化イベントを発生させます。これらは拡張機能によってのみ観察されます。これがCDIポータブルエクステンションが登場するところです。

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

3 Mavenの依存関係

CDI APIに必要な依存関係を pom.xml に追加することから始めましょう。空のエクステンションを実装するのに十分です。

<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>

theの新しいバージョンがあるかどうかを確認できます。 API およびhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jboss.weld.se%22%20AND%20a%3A%22weld-se-core%22[実装]はMaven Centralでリリースされました。

4非CDI環境でのFlywayの実行

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

それでは、https://flywaydb.org/getstarted/firststeps/api[公式サイト Flyway ]____から抜粋した次の例を見てみましょう。

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

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

私たちのCDIポータブルエクステンションは後に Flyway Datasource Beanを生成します。このサンプルでは、​​埋め込みH2データベースを使用し、 DataSourceDefinition アノテーションを通じて DataSource プロパティを提供します。

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

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

その後、次のステップを実行します。

  1. スキャン処理が始まる前に BeforeBeanDiscovery イベントを発生させます.

  2. アーカイブBeansをスキャンするタイプ検出を実行します.

検出された各型が ProcessAnnotatedType イベントを発生させます。 。 AfterTypeDiscovery イベントを発生させます

  1. Beanディスカバリーを実行します

  2. __AfterBeanDiscovery __eventを発生させます

  3. Beanの検証を実行し、定義エラーを検出します

  4. AfterDeploymentValidation イベントを発生させます

CDIポータブルエクステンションの目的は、これらのイベントを監視し、検出されたBeanに関するメタデータを確認し、このメタデータを変更するか、または追加することです。

  • CDIポータブルエクステンションでは、これらのイベントのみを観察できます。

6. CDI Portable Extensionを書く

独自のCDIポータブルエクステンションを構築することで、これらのイベントのいくつかにどのように対処できるかを見てみましょう。

6.1. SPIプロバイダの実装

CDIポータブルエクステンションは、インタフェース javax.enterprise.inject.spi.Extension. のJava SPIプロバイダです。JavaSPIの概要については、https://www.baeldung.com/java-spi[この記事]をご覧ください。

まず、 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() observerメソッドで行われます。

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

ここでは、 Flyway クラスに関するメタデータを追加しました。 ** これ以降は、コンテナによってスキャンされたかのように動作します。

次に、 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. と付けています

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

@ApplicationScoped
@FlywayType
public class Flyway {

   //...
    @Inject
    public void setDataSource(DataSource dataSource) {
     //...
    }
}

次のステップは、 Flyway Beanが DataSource Beanに依存しているため、 DataSource Beanを注入可能にすることです。

そのためには、 DataSource Beanをコンテナに登録する処理を行い、 AfterBeanDiscovery イベントを使用します。

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")

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

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

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

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

7.まとめ

CDIポータブルエクステンションを作成するのは最初は難しいようですが、コンテナー初期化ライフサイクルとエクステンション専用のSPIを理解すれば、Java EE上でフレームワークを構築するために使用できる非常に強力なツールになります。

いつものように、この記事に示されているすべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/flyway-cdi-extension[GitHubで利用可能]です。