Extension portable CDI et voie de migration

CDI Extension portable et voie de migration

1. Vue d'ensemble

Dans ce tutoriel, nous allons passer en revue une fonctionnalité intéressante de CDI (Context and Dependency Injection) appelée extension portable CDI.

Tout d'abord, nous allons commencer par comprendre comment cela fonctionne, puis nous verrons comment écrire une extension. Nous allons passer par les étapes de mise en œuvre d'un module d'intégration CDI pour Flyway, afin que nous puissions exécuter une migration de base de données au démarrage d'un conteneur CDI.

Ce tutoriel suppose une compréhension de base de CDI. Jetez un œil àthis article pour une introduction à CDI.

2. Qu'est-ce qu'une extension portable CDI?

Une extension portable CDI est un mécanisme par lequel nous pouvons implémenter des fonctionnalités supplémentaires sur le conteneur CDI. Au moment de l’amorçage, le conteneur CDI analyse le chemin de classe et crée des métadonnées sur les classes découvertes.

During this scanning process, the CDI container fires many initialization events which can be only observed by extensions. C'est ici qu'une extension portable CDI entre en jeu.

Une extension CDI Portable observe ces événements, puis modifie ou ajoute des informations aux métadonnées créées par le conteneur.

3. Dépendances Maven

Commençons par ajouter la dépendance requise pour l'API CDI dans lespom.xml. C'est suffisant pour mettre en œuvre une extension vide.


    javax.enterprise
    cdi-api
    2.0.SP1

Et pour exécuter l'application, nous pouvons utiliser n'importe quelle implémentation CDI conforme. Dans cet article, nous utiliserons la mise en œuvre de Weld.


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

Vous pouvez vérifier si de nouvelles versions dethe API etthe implementation ont été publiées sur Maven Central.

4. Exécution de Flyway dans un environnement non CDI

Avant de commencer à intégrerFlyway et CDI, nous devons d'abord regarder comment l'exécuter dans un contexte non CDI.

Jetons donc un œil à l'exemple suivant tiré deshttps://flywaydb.org/getstarted/firststeps/api:

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

Comme nous pouvons le voir, nous n'utilisons qu'une instanceFlyway qui a besoin d'une instanceDataSource.

Notre extension portable CDI produira plus tard les beansFlyway etDatasource. Pour les besoins de cet exemple, nous utiliserons une base de données H2 intégrée et nous fournirons les propriétés deDataSource via l'annotationDataSourceDefinition.

5. Événements d'initialisation du conteneur CDI

Au démarrage de l'application, le conteneur CDI commence par charger et instancier toutes les extensions portables CDI. Ensuite, dans chaque extension, il recherche et enregistre les méthodes d'observateur d'événements d'initialisation, le cas échéant. Après cela, il effectue les étapes suivantes:

  1. Déclenche l'événementBeforeBeanDiscovery avant le début du processus d'analyse

  2. Effectue la découverte de type dans laquelle il analyse les beans archive et pour chaque type découvert, il déclenche l'événementProcessAnnotatedType

  3. Déclenche l'événementAfterTypeDiscovery

  4. Effectue la découverte du haricot

  5. Tire le septièmeAfterBeanDiscovery 

  6. Effectue la validation du bean et détecte les erreurs de définition

  7. Déclenche l'événementAfterDeploymentValidation

L’intention d’une extension portable CDI est alors d’observer ces événements, de vérifier les métadonnées sur les beans découverts, de modifier ces métadonnées ou d’y ajouter des méta-données.

Dans une extension portable CDI, nous ne pouvons observer que ces événements.

6. Écriture de l'extension portable CDI

Voyons comment nous pouvons nous connecter à certains de ces événements en créant notre propre extension portable CDI.

6.1. Implémentation du fournisseur SPI

Une extension portable CDI est un fournisseur Java SPI de l'interfacejavax.enterprise.inject.spi.Extension. Jetez un œil àthis article pour une introduction à Java SPI.

Tout d'abord, nous commençons par fournir l'implémentation deExtension. Plus tard, nous ajouterons des méthodes d'observation aux événements d'amorçage du conteneur CDI:

public class FlywayExtension implements Extension {
}

Ensuite, nous ajoutons un nom de fichierMETA-INF/services/javax.enterprise.inject.spi.Extension avec ce contenu:

com.example.cdi.extension.FlywayExtension

En tant que SPI, ceExtension est chargé avant le bootstrap du conteneur. Ainsi, les méthodes observateur sur les événements d'amorçage CDI peuvent être enregistrées.

6.2. Définition des méthodes d'observateur pour les événements d'initialisation

Dans cet exemple, nous faisons connaître la classeFlyway au conteneur CDI avant le début du processus d'analyse. Ceci est fait dans la méthode d'observateur deregisterFlywayType():

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

Ici, nous avons ajouté des métadonnées sur la classeFlyway. From now on, it’ll behave as if it was scanned by the container. Pour cela, nous avons utilisé la méthodeaddAnnotatedType().

Ensuite, nous allons observer l'événementProcessAnnotatedType pour faire de la classeFlyway un bean géré CDI:

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

Tout d'abord, nous annotons la classeFlyway avec les annotations@ApplicationScoped et@FlywayType, puis nous recherchons la méthodeFlyway.setDataSource(DataSource dataSource) et nous l'annotons par@Inject.

Le résultat final des opérations ci-dessus a le même effet que si le conteneur analysait le beanFlyway suivant:

@ApplicationScoped
@FlywayType
public class Flyway {

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

L'étape suivante consiste alors à rendre un beanDataSource disponible pour l'injection car notre beanFlyway dépend d'un beanDataSource.

Pour cela, nous allons procéder pour enregistrer un BeanDataSource dans le conteneur et nous utiliserons l'événementAfterBeanDiscovery:

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

Comme nous pouvons le voir, nous avons besoin d'unDataSourceDefinition qui fournit les propriétés DataSource.

Nous pouvons annoter n'importe quel bean géré avec l'annotation suivante:

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

Pour extraire ces propriétés, nous observons l'événementProcessAnnotatedType avec l'annotation@WithAnnotations:

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

Et enfin, nous écoutons l'événementAfterDeployementValidation pour obtenir le beanFlyway voulu du conteneur CDI, puis invoquons la méthodemigrate():

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

7. Conclusion

Construire une extension portable CDI semble difficile au premier abord, mais une fois que nous avons compris le cycle de vie de l’initialisation du conteneur et le SPI dédié aux extensions, il devient un outil très puissant que nous pouvons utiliser pour construire des infrastructures au-dessus de Java EE.

Comme d'habitude, tous les exemples de code présentés dans cet article peuvent être trouvésover on GitHub.