Introduzione al modello di notifica degli eventi in CDI 2.0

1. Panoramica

CDI (Contexts and Dependency Injection) è il framework standard di inserimento delle dipendenze della piattaforma Jakarta EE.

In questo tutorial, daremo un'occhiata a CDI 2.0 e come si basa sul potente meccanismo di iniezione indipendente dai tipi di CDI 1.x aggiungendo un modello di notifica degli eventi migliorato e completo.

2. Le dipendenze di Maven

Per iniziare, creeremo un semplice progetto Maven.

Abbiamo bisogno di un contenitore conforme a CDI 2.0 e Weld, l'implementazione di riferimento di CDI, è una buona soluzione:

  javax.enterprise cdi-api 2.0.SP1   org.jboss.weld.se weld-se-core 3.0.5.Final   

Come al solito, possiamo estrarre le ultime versioni di cdi-api e weld-se-core da Maven Central.

3. Osservazione e gestione di eventi personalizzati

In poche parole, il modello di notifica degli eventi CDI 2.0 è un'implementazione classica del pattern Observer , basato sull'annotazione del parametro del metodo @Observes . Quindi, ci consente di definire facilmente i metodi dell'osservatore, che possono essere chiamati automaticamente in risposta a uno o più eventi.

Ad esempio, potremmo definire uno o più bean, che innescherebbero uno o più eventi specifici, mentre altri bean riceverebbero notifiche sugli eventi e reagirebbero di conseguenza.

Per dimostrare più chiaramente come funziona, costruiremo un semplice esempio, includendo una classe di servizio di base, una classe di eventi personalizzati e un metodo osservatore che reagisce ai nostri eventi personalizzati.

3.1. Una classe di servizio di base

Cominciamo creando una semplice classe TextService :

public class TextService { public String parseText(String text) { return text.toUpperCase(); } } 

3.2. Una classe evento personalizzata

Successivamente, definiamo una classe di eventi di esempio, che accetta un argomento String nel suo costruttore:

public class ExampleEvent { private final String eventMessage; public ExampleEvent(String eventMessage) { this.eventMessage = eventMessage; } // getter }

3.3. Definizione di un metodo osservatore con l' annotazione @Observes

Ora che abbiamo definito le nostre classi di servizio ed evento, usiamo l' annotazione @Observes per creare un metodo osservatore per la nostra classe ExampleEvent :

public class ExampleEventObserver { public String onEvent(@Observes ExampleEvent event, TextService textService) { return textService.parseText(event.getEventMessage()); } }

Mentre a prima vista, l'implementazione del metodo onEvent () sembra abbastanza banale, in realtà incapsula molte funzionalità attraverso l' annotazione @Observes .

Come possiamo vedere, il metodo onEvent () è un gestore di eventi che accetta oggetti ExampleEvent e TextService come argomenti.

Teniamo presente che tutti gli argomenti specificati dopo l' annotazione @Observes sono punti di iniezione standard. Di conseguenza, CDI creerà istanze completamente inizializzate per noi e le inietterà nel metodo dell'osservatore.

3.4. Inizializzazione del nostro contenitore CDI 2.0

A questo punto, abbiamo creato il nostro servizio e le classi di eventi e abbiamo definito un semplice metodo di osservazione per reagire ai nostri eventi. Ma come istruiamo CDI a iniettare queste istanze in fase di esecuzione?

Qui è dove il modello di notifica degli eventi mostra la sua funzionalità al massimo. Inizializziamo semplicemente la nuova implementazione di SeContainer e attiviamo uno o più eventi tramite il metodo fireEvent () :

SeContainerInitializer containerInitializer = SeContainerInitializer.newInstance(); try (SeContainer container = containerInitializer.initialize()) { container.getBeanManager().fireEvent(new ExampleEvent("Welcome to Baeldung!")); }

Si noti che stiamo usando gli oggetti SeContainerInitializer e SeContainer perché stiamo usando CDI in un ambiente Java SE, piuttosto che in Jakarta EE.

Tutti i metodi dell'osservatore collegati riceveranno una notifica quando l' EsempioEvent viene attivato propagando l'evento stesso.

Poiché tutti gli oggetti passati come argomenti dopo l' annotazione @Observes saranno completamente inizializzati, CDI si occuperà di collegare l'intero oggetto grafico TextService per noi, prima di iniettarlo nel metodo onEvent () .

In poche parole, abbiamo i vantaggi di un contenitore IoC indipendente dai tipi, insieme a un modello di notifica degli eventi ricco di funzionalità .

4. L' evento ContainerInitialized

Nell'esempio precedente, abbiamo utilizzato un evento personalizzato per passare un evento a un metodo osservatore e ottenere un oggetto TextService completamente inizializzato .

Ovviamente, questo è utile quando abbiamo davvero bisogno di propagare uno o più eventi in più punti della nostra applicazione.

A volte, abbiamo semplicemente bisogno di ottenere un mucchio di oggetti completamente inizializzati pronti per essere utilizzati all'interno delle nostre classi dell'applicazione , senza dover passare attraverso l'implementazione di eventi aggiuntivi.

A tal fine, CDI 2.0 fornisce la classe di eventi ContainerInitialized , che viene attivata automaticamente quando il contenitore Weld viene inizializzato .

Diamo un'occhiata a come possiamo utilizzare l' evento ContainerInitialized per trasferire il controllo alla classe ExampleEventObserver :

public class ExampleEventObserver { public String onEvent(@Observes ContainerInitialized event, TextService textService) { return textService.parseText(event.getEventMessage()); } } 

E tieni presente che la classe di eventi ContainerInitialized è specifica di Weld . Quindi, avremo bisogno di rifattorizzare i nostri metodi osservatore se usiamo un'implementazione CDI diversa.

5. Metodi dell'osservatore condizionale

Nella sua attuale implementazione, la nostra classe ExampleEventObserver definisce per impostazione predefinita un metodo di osservazione incondizionato. Ciò significa che il metodo dell'osservatore riceverà sempre una notifica dell'evento fornito , indipendentemente dal fatto che un'istanza della classe esista o meno nel contesto corrente.

Likewise, we can define a conditional observer method by specifying notifyObserver=IF_EXISTS as an argument to the @Observes annotation:

public String onEvent(@Observes(notifyObserver=IF_EXISTS) ExampleEvent event, TextService textService) { return textService.parseText(event.getEventMessage()); } 

When we use a conditional observer method, the method will be notified of the matching event only if an instance of the class that defines the observer method exists in the current context.

6. Transactional Observer Methods

We can also fire events within a transaction, such as a database update or removal operation. To do so, we can define transactional observer methods by adding the during argument to the @Observes annotation.

Each possible value of the during argument corresponds to a particular phase of a transaction:

  • BEFORE_COMPLETION
  • AFTER_COMPLETION
  • AFTER_SUCCESS
  • AFTER_FAILURE

If we fire the ExampleEvent event within a transaction, we need to refactor the onEvent() method accordingly to handle the event during the required phase:

public String onEvent(@Observes(during=AFTER_COMPLETION) ExampleEvent event, TextService textService) { return textService.parseText(event.getEventMessage()); }

A transactional observer method will be notified of the supplied event only in the matching phase of a given transaction.

7. Observer Methods Ordering

Another nice improvement included in CDI 2.0's event notification model is the ability for setting up an ordering or priority for calling observers of a given event.

We can easily define the order in which the observer methods will be called by specifying the @Priority annotation after @Observes.

To understand how this feature works, let's define another observer method, aside from the one that ExampleEventObserver implements:

public class AnotherExampleEventObserver { public String onEvent(@Observes ExampleEvent event) { return event.getEventMessage(); } }

In this case, both observer methods by default will have the same priority. Thus, the order in which CDI will invoke them is simply unpredictable.

We can easily fix this by assigning to each method an invocation priority through the @Priority annotation:

public String onEvent(@Observes @Priority(1) ExampleEvent event, TextService textService) { // ... implementation } 
public String onEvent(@Observes @Priority(2) ExampleEvent event) { // ... implementation }

Priority levels follow a natural ordering. Therefore, CDI will call first the observer method with a priority level of 1 and will invoke second the method with a priority level of 2.

Likewise, if we use the same priority level across two or more methods, the order is again undefined.

8. Asynchronous Events

In all the examples that we've learned so far, we fired events synchronously. However, CDI 2.0 allows us to easily fire asynchronous events as well. Asynchronous observer methods can then handle these asynchronous events in different threads.

We can fire an event asynchronously with the fireAsync() method:

public class ExampleEventSource { @Inject Event exampleEvent; public void fireEvent() { exampleEvent.fireAsync(new ExampleEvent("Welcome to Baeldung!")); } }

I bean attivano eventi, che sono implementazioni dell'interfaccia Event . Pertanto, possiamo iniettarli come qualsiasi altro fagiolo convenzionale .

Per gestire il nostro evento asincrono, dobbiamo definire uno o più metodi osservatore asincrono con l' annotazione @ObservesAsync :

public class AsynchronousExampleEventObserver { public void onEvent(@ObservesAsync ExampleEvent event) { // ... implementation } }

9. Conclusione

In questo articolo, abbiamo imparato come iniziare a utilizzare il modello di notifica degli eventi migliorato in bundle con CDI 2.0.

Come al solito, tutti gli esempi di codice mostrati in questo tutorial sono disponibili su GitHub.