Extension portable et voie de migration CDI

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 pour implémenter 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 didacticiel suppose une compréhension de base de CDI. Jetez un œil à cet 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 en plus du 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.

Au cours de ce processus d'analyse, le conteneur CDI déclenche de nombreux événements d'initialisation qui ne peuvent être observés que par les extensions . C'est là 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 de Maven

Commençons par ajouter la dépendance requise pour l'API CDI dans le pom.xml . C'est suffisant pour implémenter 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 l'implémentation Weld.

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

Vous pouvez vérifier si de nouvelles versions de l'API et de l'implémentation ont été publiées sur Maven Central.

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

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

Jetons donc un œil à l'exemple suivant tiré du site officiel de Flyway :

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

Comme nous pouvons le voir, nous n'utilisons qu'une instance Flyway qui a besoin d'une instance DataSource .

Notre extension portable CDI produira plus tard , les voies de migration et Datasource haricots. Pour les besoins de cet exemple, nous utiliserons une base de données H2 intégrée et nous fournirons les propriétés DataSource via l' annotation DataSourceDefinition .

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 des événements d'initialisation le cas échéant. Après cela, il effectue les étapes suivantes:

  1. Feux BeforeBeanDiscovery événement avant que le processus de balayage commence
  2. Effectue la découverte de type dans laquelle il analyse les beans archive et, pour chaque type découvert, déclenche l' événement ProcessAnnotatedType
  3. Déclenche le AfterTypeDiscovery événement
  4. Effectue la découverte du bean
  5. Déclenche le AfterBeanDiscovery événement
  6. Effectue la validation du bean et détecte les erreurs de définition
  7. Déclenche le AfterDeploymentValidation événement

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.

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'interface javax.enterprise.inject.spi.Extension. Jetez un œil à cet article pour une introduction à Java SPI.

Tout d'abord, nous commençons par fournir l' implémentation de l' extension . 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 fichier META-INF / services / javax.enterprise.inject.spi.Extension avec ce contenu:

com.baeldung.cdi.extension.FlywayExtension

En tant que SPI, cette extension est chargée avant le bootstrap du conteneur. Ainsi, les méthodes d'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 classe Flyway au conteneur CDI avant le début du processus d'analyse. Cela se fait dans la méthode d'observateur registerFlywayType () :

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

Ici, nous avons ajouté des métadonnées sur la classe Flyway . Désormais, il se comportera comme s'il avait été scanné par le conteneur. Pour cela, nous avons utilisé la méthode addAnnotatedType () .

Next, we'll observe the ProcessAnnotatedType event to make the Flyway class as a CDI managed 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); }

First, we annotate the Flyway class with @ApplicationScoped and @FlywayType annotations, then we search the Flyway.setDataSource(DataSource dataSource) method and we annotate it by @Inject.

The final result of the above operations have the same effect as if the container scans the following Flyway bean:

@ApplicationScoped @FlywayType public class Flyway { //... @Inject public void setDataSource(DataSource dataSource) { //... } }

The next step is then to make a DataSource bean available for injection as our Flyway bean depends on a DataSource bean.

For that, we'll process to register a DataSource Bean into the container and we'll use the AfterBeanDiscovery event:

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

As we can see, we need a DataSourceDefinition that provides the DataSource properties.

We can annotate any managed bean with the following annotation:

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

To extract these properties, we observe the ProcessAnnotatedType event along with the @WithAnnotations annotation:

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

And finally, we listen to the AfterDeployementValidation event to get the wanted Flyway bean from the CDI container and then invoke the migrate() method:

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 comprenons le cycle de vie d'initialisation du conteneur et le SPI dédié aux extensions, cela devient un outil très puissant que nous pouvons utiliser pour créer des frameworks au-dessus de Jakarta EE.

Comme d'habitude, tous les exemples de code présentés dans cet article se trouvent à l'adresse over sur GitHub.