Estensione e flyway portatili CDI

1. Panoramica

In questo tutorial, esamineremo una caratteristica interessante di CDI (Context and Dependency Injection) chiamata estensione portatile CDI.

Per prima cosa, inizieremo capendo come funziona, quindi vedremo come scrivere un'estensione. Passeremo attraverso i passaggi per implementare un modulo di integrazione CDI per Flyway, in modo da poter eseguire una migrazione del database all'avvio di un contenitore CDI.

Questo tutorial presuppone una conoscenza di base di CDI. Dai un'occhiata a questo articolo per un'introduzione a CDI.

2. Che cos'è un'estensione portatile CDI?

Un'estensione portatile CDI è un meccanismo mediante il quale possiamo implementare funzionalità aggiuntive sopra il contenitore CDI. Al momento del bootstrap, il contenitore CDI scansiona il classpath e crea metadati sulle classi rilevate.

Durante questo processo di scansione, il contenitore CDI genera molti eventi di inizializzazione che possono essere osservati solo dalle estensioni . È qui che entra in gioco un'estensione portatile CDI.

Un'estensione CDI Portable osserva questi eventi e quindi modifica o aggiunge informazioni ai metadati creati dal contenitore.

3. Dipendenze di Maven

Iniziamo aggiungendo la dipendenza richiesta per l'API CDI nel pom.xml . È sufficiente per implementare un'estensione vuota.

 javax.enterprise cdi-api 2.0.SP1 

E per eseguire l'applicazione, possiamo utilizzare qualsiasi implementazione CDI conforme. In questo articolo useremo l'implementazione di saldatura.

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

È possibile verificare se sono state rilasciate nuove versioni dell'API e dell'implementazione su Maven Central.

4. Esecuzione di Flyway in un ambiente non CDI

Prima di iniziare a integrare Flyway e CDI, dovremmo prima esaminare come eseguirlo in un contesto non CDI.

Diamo quindi un'occhiata al seguente esempio tratto dal sito ufficiale di Flyway :

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

Come possiamo vedere, stiamo usando solo un'istanza Flyway che necessita di un'istanza DataSource .

La nostra estensione portatile CDI produrrà in seguito i bean Flyway e Datasource . Ai fini di questo esempio, utilizzeremo un database H2 incorporato e forniremo le proprietà DataSource tramite l' annotazione DataSourceDefinition .

5. Eventi di inizializzazione del contenitore CDI

Al bootstrap dell'applicazione, il contenitore CDI inizia caricando e istanziando tutte le estensioni portatili CDI. Quindi, in ciascuna estensione, cerca e registra i metodi dell'osservatore degli eventi di inizializzazione, se presenti. Successivamente, esegue i seguenti passaggi:

  1. Incendi BeforeBeanDiscovery evento prima che inizi il processo di scansione
  2. Esegue il rilevamento del tipo in cui esegue la scansione dei bean di archivio e per ogni tipo rilevato attiva l' evento ProcessAnnotatedType
  3. Spara l'AfterTypeDiscovery evento
  4. Esegue la scoperta del fagiolo
  5. Spara l'AfterBeanDiscovery evento
  6. Esegue la convalida dei bean e rileva gli errori di definizione
  7. Spara l'AfterDeploymentValidation evento

L'intenzione di un'estensione portatile CDI è quindi quella di osservare questi eventi, controllare i metadati sui bean scoperti, modificare questi metadati o aggiungerli.

In un'estensione portatile CDI, possiamo solo osservare questi eventi.

6. Scrittura dell'estensione portatile CDI

Vediamo come possiamo agganciarci ad alcuni di questi eventi costruendo la nostra estensione portatile CDI.

6.1. Implementazione del provider SPI

Un'estensione portatile CDI è un provider SPI Java dell'interfaccia javax.enterprise.inject.spi.Extension. Dai un'occhiata a questo articolo per un'introduzione a Java SPI.

Innanzitutto, iniziamo fornendo l' implementazione dell'estensione . Successivamente, aggiungeremo metodi osservatore agli eventi bootstrap del contenitore CDI:

public class FlywayExtension implements Extension { }

Quindi, aggiungiamo un nome file META-INF / services / javax.enterprise.inject.spi.Extension con questo contenuto:

com.baeldung.cdi.extension.FlywayExtension

Come SPI, questa estensione viene caricata prima del bootstrap del contenitore. Quindi i metodi dell'osservatore sugli eventi di bootstrap CDI possono essere registrati.

6.2. Definizione dei metodi di osservazione degli eventi di inizializzazione

In questo esempio, rendiamo nota la classe Flyway al contenitore CDI prima che inizi il processo di scansione. Questo viene fatto nel metodo dell'osservatore registerFlywayType () :

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

Qui abbiamo aggiunto i metadati sulla classe Flyway . D'ora in poi, si comporterà come se fosse stato scansionato dal contenitore. A questo scopo, abbiamo utilizzato il metodo addAnnotatedType () .

Successivamente, osserveremo l' evento ProcessAnnotatedType per rendere la classe Flyway un bean gestito da 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); }

Per prima cosa, annotiamo la classe Flyway con annotazioni @ApplicationScoped e @FlywayType , quindi cerchiamo il metodo Flyway.setDataSource (DataSource dataSource) e lo annotiamo con @Inject.

Il risultato finale delle operazioni di cui sopra ha lo stesso effetto come se il contenitore scansionasse il seguente bean Flyway :

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

Il passaggio successivo consiste nel rendere un bean DataSource disponibile per l'iniezione poiché il nostro bean Flyway dipende da un bean DataSource .

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

La creazione di un'estensione portatile CDI sembra difficile alla prima volta, ma una volta compreso il ciclo di vita dell'inizializzazione del contenitore e l'SPI dedicato alle estensioni, diventa uno strumento molto potente che possiamo utilizzare per creare framework su Jakarta EE.

Come al solito, tutti gli esempi di codice mostrati in questo articolo possono essere trovati su GitHub.