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.
Vielen Dank für dein Feedback! Ich habe deine Anmerkungen ins Tutorial aufgenommen.