Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden
+49 (0) 351/850 33-0

Events mit CDI und EJB 3.1

Vor der Einführung von CDI (JSR-299/JSR-330) und EJB 3.1 war die Umsetzung von asynchronen Prozessen relativ umständlich und der Einsatz von JMS meist unumgänglich. Ich möchte hier kurz zeigen, dass es mit EJB 3.1 eine weitere Möglichkeit gibt schnell und effizient ein klassisches Beobachterpattern umzusetzen ohne auf JMS zurückgreifen zu müssen.

Dazu benötigen wir zunächst einen Service der Ereignisse (Events) erzeugen kann (Producer).

import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;

@Stateless
public class Producer {

  @Inject
  private Event<String> events;

  public void fireEvent() {
    this.events.fire("event");
  }
}

Die Klasse ist vom Typ Stateless der per Dependency Injection ein javax.enterprise.event.Event Object injiziert wird. Mit Hilfe dieser Klasse können anschließend Events verschickt werden.

Für den Empfang der Events implementieren wir einen weiteren Stateless Service der eine Methode listen besitzt. Der Parameter ist mit @Observes annotiert und wird vom System als Empfänger für Events vom Typ String erkannt. Beim Verschicken von Events werden alle potentiellen Empfängermethoden für den Eventtyp aufgerufen.

import javax.ejb.Stateless;
import javax.enterprise.event.Observes;

@Stateless
public class Consumer {

    public void listen(@ObservesString eventMessage) {
        System.out.println(eventMessage);
    }

}

Die Ausführung der listen Methode erfolgt synchron und im gleichen transaktionellen Kontext wie die fireEvent Methode. Wird bei der Verarbeitung des Events eine Ausnahme geworfen so wird die komplette Transaktion zurückgerollt (und umgekehrt). Mit der Angabe der Transaktionsphase für die Eventverarbeitung lässt sich dieser Zusammenhang steuern:

import javax.ejb.Stateless;
import javax.enterprise.event.Observes;
import javax.enterprise.event.TransactionPhase;

@Stateless
public class Consumer {

    public void listen(@Observes(during = TransactionPhase.AFTER_SUCCESS) String eventMessage) {
        System.out.println(eventMessage);
    }

}

Damit wird das Event von der listen Methode erst nach einer erfolgreichen Transaktion verarbeitet. Schlägt die Transaktion fehl wird die Methode nicht aufgerufen.

Für die Eventverarbeitung existieren die folgenden Transaktionsphasen:

  • IN_PROGRESS
  • BEFORE_COMPLETION
  • AFTER_COMPLETION
  • AFTER_SUCCESS
  • AFTER_FAILURE

Somit lassen sich z.B. auch Eventhandler schreiben die nur nach fehlgeschlagenen Transaktionen aufgerufen werden.

Mit der in EJB 3.1 neu eingeführten Annotation @Asynchronous können die Methoden zur Eventverarbeitung asynchron ausgeführt werden. Die Eventverarbeitung ist damit von der Erzeugung komplett entkoppelt:

import javax.ejb.Asynchronous;
import javax.ejb.Stateless;
import javax.enterprise.event.Observes;

@Stateless
public class Consumer {

    @Asynchronous
    public void listen(@Observes String eventMessage) {
        System.out.println(eventMessage);
    }

}

Ich habe eine kleine Webapplikation geschrieben welche den Einsatz von CDI, EJB 3.1 und JSF mit Berücksichtigung der einzelnen Transaktionsphasen demonstriert. Viel Spaß damit.
events-tutorial.zip

Mir sind paar kleinigkeiten mit deiner CDI beans aufgefallen.

public void afterFailure(@Observes(during = TransactionPhase.AFTER_FAILURE) String eventMessage) {
synchronized (this.messages) {
this.messages.add(„afterFailure: “ + eventMessage);
}
System.out.println(eventMessage);
}

Warun synchronized ?? Du bist doch in einer stateless bean. In einem stateless bean solltest du nie auf einen memeber Variable schreiben zugreifen.

Aussderdem solltest du lauf EJB restriktion auch keine this. verweden. Für dein beipiel wäre ein @Singleton geeignet.

Avatar Stefan Dieringer

Vielen Dank für dein Feedback! Ich habe deine Anmerkungen ins Tutorial aufgenommen.

Kommentar hinterlassen


Pin It on Pinterest