SeamFramework.orgCommunity Documentation
Die Web Beans Ereignisbenachrichtigungs-Facility gestattet es Web Beans auf eine völlig abgekoppelte Weise zu interagieren. Ereignis Producer bringen Ereignisse auf, die dann vom Web Bean Manager an Ereignis Observer geliefert werden. Dieses schlichte Schema klingt zwar etwas nach dem bekannten Observer/observierbar Muster, aber es gibt ein paar Überraschungen:
nicht nur sind Ereignis-Producer von Observern abgekoppelt, Observer sind auch komplett von Producern abgekoppelt,
Observer können eine Kombination von "Selektoren" festlegen, um den Satz von Ereignisbenachrichtigungen einzugrenzen, die sie erhalten und
Observer können sofort benachrichtigt werden oder sie können festlegen, dass die Lieferung des Ereignisses bis zum Abschluss der aktuellen Transaktion verschoben wird
Eine Observer-Methode ist eine Methode eines Web Beans mit einem Parameter, der @Observes
annotiert ist.
public void onAnyDocumentEvent(@Observes Document document) { ... }
Der annotierte Parameter heißt Ereignisparameter. Der Typ des Ereignisparameter ist der beobachtete Ereignistyp. Observer-Methoden können auch "Selektoren" festlegen, die nur Instanzen von von Web Beans Binding-Typen sind. Wird ein Binding-Typ als Ereignis-Selektor verwendet, so wird dies als Ereignis Binding-Typ.
@BindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Updated { ... }
Wir legen die Ereignis-Bindings der Observer-Methode durch Annotation des Ereignisparameters fest:
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
Eine Observer-Methode muss keine Ereignis-Bindings festlegen, in diesem Fall interessiert sie sich für alle Ereignisse eines bestimmten Typs. Legt sie Ereignis-Bindings fest, so interessiert sie sich nur für Ereignisse, die diese Ereignis-Bindings besitzen.
Die Observer-Methode kann zusätzliche Parameter besitzen, die gemäß der üblichen Einspeisungssemantik Web Beans Methodenparameter eingespeist werden:
public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }
Der Ereignis-Producer kann ein Ereignisbenachrichtigungs-Objekt durch Einspeisung erhalten:
@Observable Event<Document
> documentEvent
Die @Observable
-Annotation definiert implizit ein Web Bean mit Geltungsbereich @Dependent
und Deployment-Typ @Standard
mit einer durch den Web Bean Manager bereitgestellten Implementierung.
Ein Producer bringt durch Aufruf der fire()
-Methode des Event
-Interface Ereignisse auf, wobei ein Ereignisobjekt weitergegeben wird:
documentEvent.fire(document);
Ein Ereignis-Objekt kann eine Instanz einer beliebigen Java-Klasse sein, die keine Typ-Variablen oder Platzhalter-Typenparameter besitzt. Das Ereignis wird an jede Observer-Methode geliefert, die:
einen Ereignisparameter besitzt, dem das Ereignisobjekt zugeschrieben werden kann und
keine Ereignis-Bindings festlegt.
Der Web Bean Manager ruft einfach alle Observer-Methoden auf und gibt das Ereignis-Objekt als den Wert des Ereignisparameters weiter. Meldet eine Observer-Methode eine Ausnahme, so stoppt der Web Bean Manager den Aufruf von Observer-Methoden und die Ausnahme wird durch die fire()
-Methode erneut gemeldet.
Um einen "Selektor" festzulegen kann der Ereignis-Producer eine Instanz des Ereignis-Binding-Typs an die fire()
-Methode weitergeben:
documentEvent.fire( document, new AnnotationLiteral<Updated
>(){} );
Die Helferklasse AnnotationLiteral
ermöglicht die Instanziierung der Binding-Typen inline, da es andernfalls schwierig ist, die in Java zu tun.
Das Ereignis wird an jede Observer-Methode geliefert, die:
einen Ereignisparameter besitzt, dem das Ereignisobjekt zugeschrieben werden kann und
kein Ereignis-Binding festlegt außer für die an fire()
weitergegebenen Ereignis-Bindings.
Alternativ können Ereignis-Bindings durch Festlegen des Einspeisungspunkts der Ereignisbenachrichtigung festgelegt werden:
@Observable @Updated Event<Document
> documentUpdatedEvent
Dann besitzt jedes über diese Instanz abgegebene Ereignis das Event
annotierte Ereignis-Binding. Das Ereignis wird an jede Observer-Methode geliefert, die:
einen Ereignisparameter besitzt, dem das Ereignisobjekt zugeschrieben werden kann und
Kein Ereignis-Binding festlegt außer für die an fire()
oder die annotierten Ereignis-Bindings des Einspeisungspunkts für Ereignisbenachrichtigungen weitergegebenen.
Es ist oft hilfreich, einen Ereignis-Observer dynamisch zu registrieren. Die Anwendung kann das Observer
-Interface implementieren und eine Instanz mit einer Ereignisbenachrichtigung registrieren, indem die observe()
-Methode aufgerufen wird.
documentEvent.observe( new Observer<Document
>() { public void notify(Document doc) { ... } } );
Typen von Ereignis-Bindings können durch den Einspeisungspunkt für Ereignisbenachrichtigungen oder Weitergabe von Instanzen von Typen von Ereignis-Bindings an die observe()
-Methode festgelegt werden:
documentEvent.observe( new Observer<Document
>() { public void notify(Document doc) { ... } },
new AnnotationLiteral<Updated
>(){} );
Ein Ereignis-Binding-Typ kann Annotationsmitglieder besitzen:
@BindingType
@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
RoleType value();
}
Der Mitgliederwert dient der Eingrenzung von an den Observer gelieferten Nachrichten:
public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }
Typenmitglieder von Ereignis-Bindings können durch den Ereignis-Producer statisch festgelegt werden - dies erfolgt über Annotationen am Einspeisungspunkt der Ereignisbenachrichtigungen:
@Observable @Role(ADMIN) Event<LoggedIn
> LoggedInEvent;}}
Alternativ kann der Wert des Typenmitglieds des Ereignis-Bindings dynamisch durch den Ereignis-Producer bestimmt werden. Wir beginnen durch Schreiben einer abstrakten Unterklasse von AnnotationLiteral
:
abstract class RoleBinding
extends AnnotationLiteral<Role
>
implements Role {}
Der Ereignis-Producer gibt eine Instanz dieser Klasse an fire()
weiter:
documentEvent.fire( document, new RoleBinding() { public void value() { return user.getRole(); } } );
Typen von Ereignis-Bindings können kombiniert werden, zum Beispiel:
@Observable @Blog Event<Document
> blogEvent;
...
if (document.isBlog()) blogEvent.fire(document, new AnnotationLiteral<Updated
>(){});
Findet dieses Ereignis statt, so werden alle folgenden Observer-Methoden benachrichtigt:
public void afterBlogUpdate(@Observes @Updated @Blog Document document) { ... }
public void afterDocumentUpdate(@Observes @Updated Document document) { ... }
public void onAnyBlogEvent(@Observes @Blog Document document) { ... }
public void onAnyDocumentEvent(@Observes Document document) { ... }}}
Transaktionale Observers erhalten ihre Ereignisbenachrichtigungen vor oder nach der Abschlussphase der Transaktion während derer das Ereignis aufgegekommen ist. Zum Beispiel muss die folgende Observer-Methode einen Satz von Abfrageergebnissen neu laden, der im Applikationskontext gecacht ist, jedoch nur dann, wenn die den Category
-Baum aktualisierenden Transaktionen erfolgreich sind:
public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }
Es gibt drei Arten von transaktionalen Observern:
@AfterTransactionSuccess
-Observers werden während der Abschlussphase der Transaktion aufgerufen, jedoch nur bei erfolgreichem Abschluss der Transaktion
@AfterTransactionFailure
-Observer werden während der Abschlussphase der Transaktion aufgerufen, jedoch nur, wenn der erfolgreiche Abschluss der Transaktion fehlschlägt
@AfterTransactionCompletion
-Observer werden während der Nach-Abschlussphase der Transaktion aufgerufen
@BeforeTransactionCompletion
-Observer werden während der Vor-Abschlussphase der Transaktion aufgerufen
Transaktional Observer sind in einem "stateful" Objektmodell wie Web Beans sehr wichtig, da der Status oft länger bestehen bleibt als eine einzelne atomare Transaktion.
Stellen wir uns vor, wir besitzen einen gecachten Satz von JPA-Abfrageergebnissen im Geltungsbereich der Anwendung:
@ApplicationScoped @Singleton
public class Catalog {
@PersistenceContext EntityManager em;
List<Product
> products;
@Produces @Catalog
List<Product
> getCatalog() {
if (products==null) {
products = em.createQuery("select p from Product p where p.deleted = false")
.getResultList();
}
return products;
}
}
Von Zeit zu Zeit wird ein Product
erstellt oder gelöscht. Ist dies der Fall, so müssen wir den Product
-Katalog neu laden. Wir sollten damit aber bis nach dem erfolgreichen Abschluss der Transaktion warten!
Das Web Bean, das Product
s erstellt oder löscht könnte Ereignisse aufrufen, zum Beispiel:
@Stateless
public class ProductManager {
@PersistenceContext EntityManager em;
@Observable Event<Product
> productEvent;
public void delete(Product product) {
em.delete(product);
productEvent.fire(product, new AnnotationLiteral<Deleted
>(){});
}
public void persist(Product product) {
em.persist(product);
productEvent.fire(product, new AnnotationLiteral<Created
>(){});
}
...
}
Und jetzt kann Catalog
die Ereignisse nach erfolgreichem Abschluss der Transaktion beobachten:
@ApplicationScoped @Singleton
public class Catalog {
...
void addProduct(@AfterTransactionSuccess @Observes @Created Product product) {
products.add(product);
}
void addProduct(@AfterTransactionSuccess @Observes @Deleted Product product) {
products.remove(product);
}
}