Introduction au modèle de notification d'événements dans CDI 2.0

1. Vue d'ensemble

CDI (Contexts and Dependency Injection) est le framework d'injection de dépendances standard de la plateforme Jakarta EE.

Dans ce didacticiel, nous examinerons CDI 2.0 et comment il s'appuie sur le puissant mécanisme d'injection de type sécurisé de CDI 1.x en ajoutant un modèle de notification d'événement amélioré et complet.

2. Les dépendances de Maven

Pour commencer, nous allons créer un projet Maven simple.

Nous avons besoin d'un conteneur compatible CDI 2.0 et Weld, l'implémentation de référence de CDI, convient parfaitement:

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

Comme d'habitude, nous pouvons extraire les dernières versions de cdi-api et weld-se-core de Maven Central.

3. Observation et gestion des événements personnalisés

En termes simples, le modèle de notification d'événement CDI 2.0 est une implémentation classique du modèle Observer , basée sur l' annotation de paramètre de méthode @Observes . Par conséquent, cela nous permet de définir facilement des méthodes d'observateurs, qui peuvent être automatiquement appelées en réponse à un ou plusieurs événements.

Par exemple, nous pourrions définir un ou plusieurs beans, qui déclencheraient un ou plusieurs événements spécifiques, tandis que d'autres beans seraient notifiés des événements et réagiraient en conséquence.

Pour démontrer plus clairement comment cela fonctionne, nous allons créer un exemple simple, comprenant une classe de service de base, une classe d'événements personnalisée et une méthode d'observateur qui réagit à nos événements personnalisés.

3.1. Une classe de service de base

Commençons par créer une classe TextService simple :

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

3.2. Une classe d'événement personnalisée

Ensuite, définissons un exemple de classe d'événements, qui prend un argument String dans son constructeur:

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

3.3. Définition d'une méthode d'observateur avec l' annotation @Observes

Maintenant que nous avons défini nos classes de service et d'événement, utilisons l' annotation @Observes pour créer une méthode d'observateur pour notre classe ExampleEvent :

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

Alors qu'à première vue, l'implémentation de la méthode onEvent () semble assez triviale, elle encapsule en fait beaucoup de fonctionnalités via l' annotation @Observes .

Comme on peut le voir, la onEvent () méthode est un gestionnaire d'événements qui prend ExampleEvent et TextService objets comme arguments.

Gardons à l'esprit que tous les arguments spécifiés après l' annotation @Observes sont des points d'injection standard. En conséquence, CDI créera pour nous des instances entièrement initialisées et les injectera dans la méthode observer.

3.4. Initialisation de notre conteneur CDI 2.0

À ce stade, nous avons créé nos classes de service et d'événements, et nous avons défini une méthode d'observateur simple pour réagir à nos événements. Mais comment demander à CDI d'injecter ces instances au moment de l'exécution?

C'est ici que le modèle de notification d'événements montre ses fonctionnalités au maximum. Nous initialisons simplement la nouvelle implémentation de SeContainer et déclenchons un ou plusieurs événements via la méthode fireEvent () :

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

Notez que nous utilisons les objets SeContainerInitializer et SeContainer car nous utilisons CDI dans un environnement Java SE, plutôt que dans Jakarta EE.

Toutes les méthodes d'observateur attachées seront notifiées lorsque ExampleEvent est déclenché en propageant l'événement lui-même.

Puisque tous les objets passés en arguments après l' annotation @Observes seront entièrement initialisés, CDI se chargera de câbler tout le graphe d'objets TextService pour nous, avant de l'injecter dans la méthode onEvent () .

En un mot, nous bénéficions des avantages d'un conteneur IoC de type sécurisé, ainsi que d'un modèle de notification d'événements riche en fonctionnalités .

4. L' événement ContainerInitialized

Dans l'exemple précédent, nous avons utilisé un événement personnalisé pour transmettre un événement à une méthode d'observateur et obtenir un objet TextService entièrement initialisé .

Bien sûr, cela est utile lorsque nous avons vraiment besoin de propager un ou plusieurs événements sur plusieurs points de notre application.

Parfois, nous avons simplement besoin d'obtenir un tas d'objets entièrement initialisés prêts à être utilisés dans nos classes d'application , sans avoir à passer par l'implémentation d'événements supplémentaires.

À cette fin, CDI 2.0 fournit la classe d'événements ContainerInitialized , qui est automatiquement déclenchée lorsque le conteneur Weld est initialisé .

Voyons comment nous pouvons utiliser l' événement ContainerInitialized pour transférer le contrôle vers la classe ExampleEventObserver :

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

Et gardez à l'esprit que la classe d'événements ContainerInitialized est spécifique à Weld . Nous devrons donc refactoriser nos méthodes d'observation si nous utilisons une implémentation CDI différente.

5. Méthodes d'observateur conditionnel

Dans son implémentation actuelle, notre classe ExampleEventObserver définit par défaut une méthode d'observateur inconditionnel. Cela signifie que la méthode observer sera toujours notifiée de l'événement fourni , indépendamment du fait qu'une instance de la classe existe ou non dans le contexte actuel.

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

Les Beans déclenchent des événements, qui sont des implémentations de l' interface Event . Par conséquent, nous pouvons les injecter comme n'importe quel autre haricot conventionnel .

Pour gérer notre événement asynchrone, nous devons définir une ou plusieurs méthodes d'observateur asynchrones avec l' annotation @ObservesAsync :

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

9. Conclusion

Dans cet article, nous avons appris comment commencer à utiliser le modèle de notification d'événement amélioré fourni avec CDI 2.0.

Comme d'habitude, tous les exemples de code présentés dans ce didacticiel sont disponibles à l'adresse over sur GitHub.