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

CDIポータブル拡張機能およびフライウェイ

1. 概要

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

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

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

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

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

During this scanning process, the CDI container fires many initialization events which can be only observed by extensions。 これが、CDIポータブル拡張機能の出番です。

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

3. Mavenの依存関係

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


    javax.enterprise
    cdi-api
    2.0.SP1

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


    org.jboss.weld.se
    weld-se-core
    3.0.5.Final
    runtime

the APIthe implementationの新しいバージョンがMavenCentralでリリースされているかどうかを確認できます。

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

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

それでは、https://flywaydb.org/getstarted/firststeps/api:から抜粋した次の例を見てみましょう。

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 eventを起動します

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

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

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

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

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

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

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

CDIポータブル拡張機能は、インターフェースjavax.enterprise.inject.spi.Extension.のJava SPIプロバイダーです。JavaSPIの概要については、this articleをご覧ください。

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

public class FlywayExtension implements Extension {
}

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

com.example.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クラスに関するメタデータを追加しました。 From now on, it’ll behave as if it was scanned by the container.この目的のために、addAnnotatedType()メソッドを使用しました。

次に、ProcessAnnotatedTypeイベントを観察して、FlywayクラスをCDIマネージドBeanとして作成します。

public void processAnnotatedType(@Observes ProcessAnnotatedType patEvent) {
    patEvent.configureAnnotatedType()
      .add(ApplicationScoped.Literal.INSTANCE)
      .add(new AnnotationLiteral() {})
      .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) {
      //...
    }
}

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

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

void afterBeanDiscovery(@Observes AfterBeanDiscovery abdEvent, BeanManager bm) {
    abdEvent.addBean()
      .types(javax.sql.DataSource.class, DataSource.class)
      .qualifiers(new AnnotationLiteral() {}, new AnnotationLiteral() {})
      .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コンテナーから取得し、migrate()メソッドを呼び出します。

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

7. 結論

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

いつものように、この記事に示されているすべてのコードサンプルはover on GitHubで見つけることができます。