CDI Portable Extension und Flyway

CDI Portable Extension und Flyway

1. Überblick

In diesem Tutorial werden wir eine interessante Funktion von CDI (Context and Dependency Injection) behandeln, die als tragbare CDI-Erweiterung bezeichnet wird.

Zuerst verstehen wir, wie es funktioniert, und dann sehen wir, wie man eine Erweiterung schreibt. Wir werden die Schritte zum Implementieren eines CDI-Integrationsmoduls für Flyway ausführen, damit wir beim Start eines CDI-Containers eine Datenbankmigration ausführen können.

Dieses Tutorial setzt ein grundlegendes Verständnis von CDI voraus. Schauen Sie sichthis article an, um eine Einführung in CDI zu erhalten.

2. Was ist eine tragbare CDI-Erweiterung?

Eine tragbare CDI-Erweiterung ist ein Mechanismus, mit dem wir zusätzliche Funktionen über den CDI-Container implementieren können. Zur Bootstrap-Zeit durchsucht der CDI-Container den Klassenpfad und erstellt Metadaten zu den erkannten Klassen.

During this scanning process, the CDI container fires many initialization events which can be only observed by extensions. Hier kommt eine tragbare CDI-Erweiterung ins Spiel.

Eine CDI Portable-Erweiterung beobachtet diese Ereignisse und ändert oder fügt dann Informationen zu den vom Container erstellten Metadaten hinzu.

3. Maven-Abhängigkeiten

Beginnen wir mit dem Hinzufügen der erforderlichen Abhängigkeit für die CDI-API inpom.xml. Es reicht aus, um eine leere Erweiterung zu implementieren.


    javax.enterprise
    cdi-api
    2.0.SP1

Für die Ausführung der Anwendung können wir jede kompatible CDI-Implementierung verwenden. In diesem Artikel verwenden wir die Weld-Implementierung.


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

Sie können überprüfen, ob neue Versionen vonthe API undthe implementation in Maven Central veröffentlicht wurden.

4. Ausführen von Flyway in einer Nicht-CDI-Umgebung

Bevor wir mit der Integration vonFlyway und CDI beginnen, sollten wir uns zunächst ansehen, wie es in einem Nicht-CDI-Kontext ausgeführt wird.

Schauen wir uns also das folgende Beispiel aushttps://flywaydb.org/getstarted/firststeps/api: an

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

Wie wir sehen können, verwenden wir nur eineFlyway-Instanz, die eineDataSource-Instanz benötigt.

Unsere tragbare CDI-Erweiterung wird später die BohnenFlyway undDatasource produzieren. Für den Zweck dieses Beispiels verwenden wir eine eingebettete H2-Datenbank und stellen die Eigenschaften vonDataSourceüber die Annotation vonDataSourceDefinitionbereit.

5. CDI-Container-Initialisierungsereignisse

Beim Bootstrap der Anwendung wird der CDI-Container gestartet, indem alle tragbaren CDI-Erweiterungen geladen und instanziiert werden. In jeder Erweiterung werden dann Beobachtermethoden für Initialisierungsereignisse (sofern vorhanden) gesucht und registriert. Danach führt es die folgenden Schritte aus:

  1. Löst das EreignisBeforeBeanDiscoveryaus, bevor der Scanvorgang beginnt

  2. Führt die Typerkennung durch, bei der Archiv-Beans gescannt werden, und löst für jeden erkannten Typ das EreignisProcessAnnotatedTypeaus

  3. Löst das EreignisAfterTypeDiscoveryaus

  4. Führt die Bean-Erkennung durch

  5. Feuert dieAfterBeanDiscovery iebenten ab

  6. Führt eine Bean-Validierung durch und erkennt Definitionsfehler

  7. Löst das EreignisAfterDeploymentValidationaus

Die Absicht einer tragbaren CDI-Erweiterung ist es dann, diese Ereignisse zu beobachten, Metadaten zu den ermittelten Beans zu überprüfen, diese Metadaten zu ändern oder zu ergänzen.

In einer tragbaren CDI-Erweiterung können wir nur diese Ereignisse beobachten.

6. Schreiben der tragbaren CDI-Erweiterung

Lassen Sie uns sehen, wie wir uns an einige dieser Ereignisse anschließen können, indem wir unsere eigene tragbare CDI-Erweiterung erstellen.

6.1. Implementieren des SPI-Providers

Eine tragbare CDI-Erweiterung ist ein Java SPI-Anbieter der Schnittstellejavax.enterprise.inject.spi.Extension.. Eine Einführung in Java SPI finden Sie unterthis article.

Zunächst stellen wir die Implementierung vonExtensionbereit. Später werden wir den Bootstrap-Ereignissen des CDI-Containers Beobachtermethoden hinzufügen:

public class FlywayExtension implements Extension {
}

Dann fügen wir einen DateinamenMETA-INF/services/javax.enterprise.inject.spi.Extension mit diesem Inhalt hinzu:

com.example.cdi.extension.FlywayExtension

Als SPI wird diesesExtension vor dem Container-Bootstrap geladen. So können Beobachtermethoden für die CDI-Bootstrap-Ereignisse registriert werden.

6.2. Definieren von Beobachtermethoden für Initialisierungsereignisse

In diesem Beispiel machen wir die KlasseFlywaydem CDI-Container bekannt, bevor der Scanvorgang beginnt. Dies erfolgt nach der Beobachtermethode vonregisterFlywayType():

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

Hier haben wir Metadaten zur KlasseFlywayhinzugefügt. From now on, it’ll behave as if it was scanned by the container. Zu diesem Zweck haben wir die MethodeaddAnnotatedType() verwendet.

Als Nächstes beobachten wir das EreignisProcessAnnotatedType, um die KlasseFlywayals CDI-verwaltete Bean zu definieren:

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);
}

Zuerst kommentieren wir dieFlyway-Klasse mit@ApplicationScoped und@FlywayType Annotationen, dann durchsuchen wir dieFlyway.setDataSource(DataSource dataSource)-Methode und kommentieren sie mit@Inject.

Das Endergebnis der obigen Operationen hat den gleichen Effekt, als ob der Container die folgendeFlyway-Bean scannt:

@ApplicationScoped
@FlywayType
public class Flyway {

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

Der nächste Schritt besteht dann darin, eineDataSource-Bohne zur Injektion verfügbar zu machen, da unsereFlyway-Bohne von einerDataSource-Bohne abhängt.

Dazu registrieren wir eineDataSource Bean im Container und verwenden dasAfterBeanDiscovery Ereignis:

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;
      });
}

Wie wir sehen können, benötigen wir einDataSourceDefinition, das die DataSource-Eigenschaften bereitstellt.

Wir können jede verwaltete Bean mit der folgenden Anmerkung versehen:

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

Um diese Eigenschaften zu extrahieren, beobachten wir das EreignisProcessAnnotatedTypezusammen mit der Annotation@WithAnnotations:

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

Und schließlich hören wir uns das EreignisAfterDeployementValidationan, um die gewünschteFlyway-Bean aus dem CDI-Container abzurufen, und rufen dann die Methodemigrate()auf:

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

7. Fazit

Das Erstellen einer portablen CDI-Erweiterung scheint auf den ersten Blick schwierig zu sein. Wenn wir jedoch den Lebenszyklus der Containerinitialisierung und das SPI für Erweiterungen verstanden haben, wird dies zu einem sehr leistungsstarken Tool, mit dem wir Frameworks auf der Basis von Java EE erstellen können.

Wie üblich finden Sie alle in diesem Artikel gezeigten Codebeispiele inover on GitHub.