Einführung in das Ereignisbenachrichtigungsmodell in CDI 2.0

1. Übersicht

CDI (Contexts and Dependency Injection) ist das Standard-Framework für die Abhängigkeitsinjektion der Jakarta EE-Plattform.

In diesem Tutorial werfen wir einen Blick auf CDI 2.0 und wie es auf dem leistungsstarken, typsicheren Injektionsmechanismus von CDI 1.x aufbaut, indem ein verbessertes, voll funktionsfähiges Ereignisbenachrichtigungsmodell hinzugefügt wird.

2. Die Maven-Abhängigkeiten

Zunächst erstellen wir ein einfaches Maven-Projekt.

Wir benötigen einen CDI 2.0-kompatiblen Container, und Weld, die Referenzimplementierung von CDI, passt gut dazu:

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

Wie üblich können wir die neuesten Versionen von cdi-api und Weld-Se-Core von Maven Central beziehen.

3. Beobachten und Behandeln von benutzerdefinierten Ereignissen

Einfach ausgedrückt ist das CDI 2.0-Ereignisbenachrichtigungsmodell eine klassische Implementierung des Observer-Musters , das auf der Annotation von @Observes- Methodenparametern basiert . Daher können wir auf einfache Weise Beobachtermethoden definieren, die automatisch als Reaktion auf ein oder mehrere Ereignisse aufgerufen werden können.

Zum Beispiel könnten wir eine oder mehrere Beans definieren, die ein oder mehrere spezifische Ereignisse auslösen würden, während andere Beans über die Ereignisse benachrichtigt würden und entsprechend reagieren würden.

Um klarer zu demonstrieren, wie dies funktioniert, erstellen wir ein einfaches Beispiel, einschließlich einer Basisdienstklasse, einer benutzerdefinierten Ereignisklasse und einer Beobachtermethode, die auf unsere benutzerdefinierten Ereignisse reagiert.

3.1. Eine Basisdienstklasse

Beginnen wir mit der Erstellung einer einfachen TextService- Klasse:

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

3.2. Eine benutzerdefinierte Ereignisklasse

Als nächstes definieren wir eine Beispielereignisklasse, die ein String- Argument in ihrem Konstruktor verwendet:

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

3.3. Definieren einer Observer-Methode mit der Annotation @Observes

Nachdem wir unsere Service- und Ereignisklassen definiert haben, verwenden wir die Annotation @Observes , um eine Beobachtermethode für unsere ExampleEvent- Klasse zu erstellen :

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

Während die Implementierung der onEvent () -Methode auf den ersten Blick ziemlich trivial aussieht, kapselt sie tatsächlich eine Menge Funktionen durch die Annotation @Observes .

Wie wir sehen können, ist die onEvent () -Methode ein Ereignishandler, der ExampleEvent- und TextService- Objekte als Argumente verwendet.

Beachten Sie, dass alle nach der Annotation @Observes angegebenen Argumente Standardinjektionspunkte sind. Infolgedessen erstellt CDI vollständig initialisierte Instanzen für uns und fügt sie in die Beobachtermethode ein.

3.4. Initialisierung unseres CDI 2.0-Containers

Zu diesem Zeitpunkt haben wir unsere Service- und Ereignisklassen erstellt und eine einfache Beobachtermethode definiert, um auf unsere Ereignisse zu reagieren. Aber wie weisen wir CDI an, diese Instanzen zur Laufzeit zu injizieren?

Hier zeigt das Ereignisbenachrichtigungsmodell seine Funktionalität in vollen Zügen. Wir initialisieren einfach die neue SeContainer- Implementierung und lösen ein oder mehrere Ereignisse mit der fireEvent () -Methode aus:

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

Beachten Sie, dass wir die Objekte SeContainerInitializer und SeContainer verwenden , da wir CDI in einer Java SE-Umgebung und nicht in Jakarta EE verwenden.

Alle angehängten Beobachtermethoden werden benachrichtigt, wenn das ExampleEvent durch Weitergabe des Ereignisses selbst ausgelöst wird.

Da alle Objekte, die nach der Annotation @Observes als Argumente übergeben wurden , vollständig initialisiert werden, übernimmt CDI die Verkabelung des gesamten TextService- Objektdiagramms für uns, bevor es in die onEvent () -Methode eingefügt wird.

Kurz gesagt, wir haben die Vorteile eines typsicheren IoC-Containers sowie eines funktionsreichen Ereignismeldungsmodells .

4. Das ContainerInitialized- Ereignis

Im vorherigen Beispiel haben wir ein benutzerdefiniertes Ereignis verwendet, um ein Ereignis an eine Beobachtermethode zu übergeben und ein vollständig initialisiertes TextService- Objekt abzurufen .

Dies ist natürlich nützlich, wenn wir wirklich ein oder mehrere Ereignisse über mehrere Punkte unserer Anwendung verteilen müssen.

Manchmal müssen wir einfach eine Reihe vollständig initialisierter Objekte abrufen, die für die Verwendung in unseren Anwendungsklassen bereit sind , ohne dass zusätzliche Ereignisse implementiert werden müssen.

Zu diesem Zweck stellt CDI 2.0 die Ereignisklasse ContainerInitialized bereit , die automatisch ausgelöst wird, wenn der Schweißcontainer initialisiert wird .

Lassen Sie uns einen Blick darauf werfen, wie wir das ContainerInitialized- Ereignis zum Übertragen der Steuerung an die ExampleEventObserver- Klasse verwenden können:

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

Beachten Sie außerdem, dass die ContainerInitialized- Ereignisklasse schweißspezifisch ist . Wenn wir eine andere CDI-Implementierung verwenden, müssen wir unsere Beobachtermethoden überarbeiten.

5. Bedingte Beobachtermethoden

In der aktuellen Implementierung definiert unsere ExampleEventObserver- Klasse standardmäßig eine bedingungslose Beobachtermethode. Dies bedeutet, dass die Beobachtermethode immer über das bereitgestellte Ereignis informiert wird , unabhängig davon, ob im aktuellen Kontext eine Instanz der Klasse vorhanden ist oder nicht.

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

Beans lösen Ereignisse aus, die Implementierungen der Ereignisschnittstelle sind . Daher können wir sie wie jede andere herkömmliche Bohne injizieren .

Um unser asynchrones Ereignis zu behandeln, müssen wir eine oder mehrere asynchrone Beobachtermethoden mit der Annotation @ObservesAsync definieren :

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

9. Fazit

In diesem Artikel haben wir gelernt, wie Sie mit dem in CDI 2.0 enthaltenen verbesserten Ereignisbenachrichtigungsmodell beginnen.

Wie üblich sind alle in diesem Tutorial gezeigten Codebeispiele auf GitHub verfügbar.