Introduction au modèle de notification d’événement dans CDI 2.0

1. Vue d’ensemble

CDI (Contexts and Dependency Injection) est le cadre d’injection de dépendance standard de la plate-forme Jakarta EE.

Dans ce tutoriel, nous allons jeter un coup d’œil à http://www.cdi-spec.org/news/2017/05/15/CDI 2 is__released/[CDI 2.0]et à la manière dont il s’appuie sur le puissant mécanisme d’injection sûr pour le type de CDI 1.x en ajoutant un modèle amélioré de notification d’événement complet.

2. Les dépendances Maven

Pour commencer, nous allons construire un simple projet Maven.

  • Nous avons besoin d’un conteneur compatible CDI 2.0, et Weld , l’implémentation de référence de CDI, convient bien: **

<dependencies>
    <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
        <version>2.0.SP1</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.weld.se</groupId>
        <artifactId>weld-se-core</artifactId>
        <version>3.0.5.Final</version>
    </dependency>
</dependencies>

Comme d’habitude, nous pouvons extraire les dernières versions de cdi-api et https://search .maven.org/search? q = g: org.jboss.weld.se% 20AND% 20a: souder-se-core & core = gav[souder-se-core] de Maven Central.

3. Observation et gestion d’événements personnalisés

Autrement dit, le modèle de notification d’événements CDI 2.0 est une implémentation classique du modèle Observer ** , basé sur le @ Observes annotation paramètre-méthode. Par conséquent, cela nous permet de définir facilement des méthodes d’observateur, qui peuvent être appelées automatiquement en réponse à un ou plusieurs événements.

Par exemple, nous pourrions définir un ou plusieurs haricots, ce qui déclencherait un ou plusieurs événements spécifiques, tandis que d’autres haricots seraient informés des événements et réagiraient en conséquence.

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

** 3.1. Une classe de service de base

**

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

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éfinir une méthode Observer avec l’annotation @ Observes

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

public class ExampleEventObserver {

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

Bien qu’à première vue, l’implémentation de la méthode onEvent () semble assez triviale, elle encapsule de nombreuses fonctionnalités par le biais de l’annotation @ Observes .

Comme nous pouvons le constater, la méthode onEvent () est un gestionnaire d’événements qui utilise les objets ExampleEvent et TextService comme arguments.

  • N’oubliez pas que tous les arguments spécifiés après l’annotation @ Observes sont des points d’injection standard. ** En conséquence, CDI créera des instances entièrement initialisées et les injectera dans la méthode observateur.

3.4. Initialisation de notre conteneur CDI 2.0

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

C’est ici que le modèle de notification d’événement montre sa fonctionnalité au maximum. Nous initialisons simplement la nouvelle implémentation 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 parce que nous utilisons CDI dans un environnement Java SE, plutôt que dans Jakarta EE. **

Toutes les méthodes observatrices attachées seront notifiées lorsque le ExampleEvent est lancé en propageant l’événement lui-même.

Etant donné que tous les objets passés en arguments après l’annotation @ Observes seront entièrement initialisés, CDI se chargera de câbler l’ensemble du graphe d’objet TextService avant de l’injecter dans la méthode onEvent () .

En résumé, nous avons les avantages d’un conteneur IoC sécurisé pour le type, 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 observateur et obtenir un objet TextService entièrement initialisé.

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

  • Parfois, nous devons simplement obtenir un ensemble d’objets entièrement initialisés prêts à être utilisés dans nos classes d’application ** , sans avoir à passer par la mise en œuvre d’événements supplémentaires.

À cette fin, CDI 2.0 fournit le http://javadox.com/org.jboss.weld.se/weld-se-core/2.2.6.Final/org/jboss/weld/environment/se/events/ContainerInitialized .html[ ContainerInitialized ], classe d’événement automatiquement déclenchée à l’initialisation du conteneur Weld .

Voyons comment 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 à la soudure . Nous devrons donc repenser nos méthodes d’observateur 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 observateur sera toujours notifiée de l’événement fourni , qu’une instance de la classe existe ou non dans le contexte actuel.

De même, nous pouvons définir une méthode d’observateur conditionnel en spécifiant notifyObserver = IF EXISTS en tant qu’argument de l’annotation @ Observes__:

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

Lorsque nous utilisons une méthode d’observateur conditionnel, la méthode ne sera informée de l’événement correspondant que si une instance de la classe qui définit la méthode observateur existe dans le contexte actuel .

6. Méthodes d’observation transactionnelles

  • Nous pouvons également déclencher des événements dans une transaction, tels qu’une opération de mise à jour ou de suppression de base de données ** . Pour ce faire, nous pouvons définir des méthodes d’observateur transactionnelles en ajoutant l’argument during à l’annotation @ Observes .

Chaque valeur possible de l’argument during correspond à une phase particulière d’une transaction:

  • BEFORE COMPLETION__

  • APRÈS ACHÈVEMENT

  • AFTER SUCCESS__

  • AFTER FAILURE__

Si nous déclenchons l’événement ExampleEvent au sein d’une transaction, nous devons refactoriser la méthode onEvent () en conséquence pour gérer l’événement au cours de la phase requise:

public String onEvent(@Observes(during=AFTER__COMPLETION) ExampleEvent event, TextService textService) {
    return textService.parseText(event.getEventMessage());
}
  • Une méthode d’observation transactionnelle sera notifiée de l’événement fourni uniquement lors de la phase de mise en correspondance d’une transaction donnée ** .

7. Méthodes Observer Commande

Une autre amélioration intéressante incluse dans le modèle de notification d’événement de CDI 2.0 est la possibilité de définir un ordre ou une priorité pour appeler des observateurs d’un événement donné.

Nous pouvons facilement définir l’ordre dans lequel les méthodes d’observateur seront appelées en spécifiant l’annotation @Priority après @ Observes .

Pour comprendre le fonctionnement de cette fonctionnalité, définissons une autre méthode observateur, hormis celle implémentée par ExampleEventObserver

public class AnotherExampleEventObserver {

    public String onEvent(@Observes ExampleEvent event) {
        return event.getEventMessage();
    }
}
  • Dans ce cas, les deux méthodes observateur par défaut auront la même priorité. Ainsi, l’ordre dans lequel CDI les invoquera est simplement imprévisible ** .

Nous pouvons facilement résoudre ce problème en attribuant à chaque méthode une priorité d’appel via l’annotation @ Priority :

public String onEvent(@Observes @Priority(1) ExampleEvent event, TextService textService) {
   //... implementation
}
public String onEvent(@Observes @Priority(2) ExampleEvent event) {
   //... implementation
}
  • Les niveaux de priorité suivent un ordre naturel. ** Par conséquent, CDI appellera d’abord la méthode observateur avec un niveau de priorité égal à 1 et invoquera ensuite la méthode avec un niveau de priorité égal à 2 .

De même, si nous utilisons le même niveau de priorité sur deux méthodes ou plus, l’ordre est encore indéfini .

8. Événements asynchrones

Dans tous les exemples que nous avons appris jusqu’à présent, nous avons déclenché des événements de manière synchrone. Cependant, CDI 2.0 nous permet également de déclencher facilement des événements asynchrones. Les méthodes d’observation asynchrones peuvent ensuite gérer ces événements asynchrones dans différents threads .

Nous pouvons déclencher un événement de manière asynchrone avec la méthode fireAsync () :

public class ExampleEventSource {

    @Inject
    Event<ExampleEvent> 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’observation asynchrones à l’aide de @ObservesAsync annotation:

public class AsynchronousExampleEventObserver {

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

9. Conclusion

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

Comme d’habitude, tous les exemples de code présentés dans ce tutoriel sont disponibles à l’adresse GitHub .