CDIポータブルエクステンションとフライウェイ
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ポータブル拡張機能をロードしてインスタンス化することから始まります。 次に、各拡張機能で、初期化イベントのオブザーバーメソッドがあればそれを検索して登録します。 その後、次の手順を実行します。
- スキャンプロセスが開始する前にBeforeBeanDiscoveryイベントを発生させます
- アーカイブBeanをスキャンするタイプ検出を実行し、検出されたタイプごとにProcessAnnotatedTypeイベントを発生させます。
- AfterTypeDiscoveryイベントを発生させます
- Beanディスカバリーを実行します
- AfterBeanDiscoveryイベントを発生させます
- Bean Validationを実行し、定義エラーを検出します
- 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のにあります。