Continuiamo la serie sui Web Beans, un insieme di servizi per Java EE nati per rendere più agevole lo sviluppo di applicazioni. In questo quinto articolo andremo ad analizzare con attenzione l‘importante aspetto della gestione degli eventi.
Gestione degli eventi
I Web Beans possono produrre e consumare eventi. Questa possibilità fa sì che due bean possano interagire più semplicemente fra loro in maniera disaccoppiata senza alcuna dipendenza tra loro in fase di compilazione. Un evento è costituito da un oggetto Java (event object) e da un insieme di istanze (che può anche essere vuoto) di binding types (event bindings). L’event object funge da payload per propagare lo stato dal producer al consumer. Gli event binding fungono da topic selectors, consentendo al consumer di restringere l’insieme di eventi da osservare. Un event consumer osserva solo gli eventi di un determinato type, l’observed event type, con un insieme di istanze di event binding types (gli observed event bindings). Un event object è un’istanza di classe Java concreta e senza type variables e wildcards.
Gli event type di un evento comprendono tutte le superclassi e le interfacce della classe dell’event object. Un event binding type non è altro che un comune binding type (per la definizione di binding type vedi l’articolo [2] di questa serie). L’unica differenza è che un event binding type deve essere dichiarato come @Target({FIELD, PARAMETER}). Ogni event binding type deve specificare anche l’annotation @javax.inject.BindingType. Un evento consumer viene informato del verificarsi di un evento se quest’ultimo specifica un event binding type fra quelli previsti dal consumer.
L’interfaccia javax.inject.Manager fornisce un metodo per scatenare un evento:
public interface Manager { public void fireEvent(Object event, Annotation... bindings); ... }
Il primo argomento in ingresso al metodo è l’event object. Qualora si tentasse di passare una oggetto contenente type variables e/o wildcards, verrebbe scatenata una eccezione di tipo java.lang.IllegalArgumentException . Il secondo argomento in ingresso al metodo fireEvent() è una lista (opzionale) di event binding types.
Il punto di vista dell’osservatore
Un observer consuma eventi e permette all’applicazione di reagire a tali eventi. Gli observer implementano l’interfaccia javax.event.Observer:
public interface Observer { public void notify(T event); }
Un’istanza di observer può essere registrata nel container tramite invocazione del metodo Manager.addObserver():
public interface Manager { public Manager addObserver(Observer observer, Class eventType, Annotation... bindings); public Manager addObserver(Observer observer, TypeLiteral eventType, Annotation... bindings); ... }
Il primo parametro è l’oggetto observer. Il secondo è l’observed event type. I rimanenti parametri sono observed event bindings opzionali. L’observer viene avvisato quando un event object che è attribuibile all’observed event type si è attivato. Un’istanza di observer può essere deregistrata tramite invocazione del metodo Manager.removeObserver():
public interface Manager { public Manager removeObserver(Observer observer, TypeLiteral eventType, Annotation... bindings); public Manager removeObserver(Observer observer, Class eventType, Annotation... bindings); ... }
Se due istanze dello stesso binding type vengono passate ai metodi addObserver() o removeObserver(), viene scatenata una eccezione di tipo javax.inject.DuplicateBindingTypeException.
Ogni qual volta un’applicazione scatena un evento il container deve determinare, invocando il metodo Manager.resolveObservers() e passandogli in ingresso l’event object e tutti gli event bindings, quali siano gli observer per quel determinato evento e, successivamente, invocare il metodo notify() dell’interfaccia Observer di ciascuno passandogli in ingresso l’event object.
Gli observer possono lanciare eccezioni. Se un observer lancia una eccezione, quest’ultima termina il processing dell’evento e non verrà invocato nessun altro observer di quell’evento.
Observer methods
Un observer method è un observer definito attraverso annotazioni piuttosto che come implementazione esplicita dell’interfaccia Observer. A differenza degli observer, gli observer method vengono percepiti ed automaticamente registrati dal container.
Un observer method deve essere un metodo di una simple bean class o di una session bean class. Può essere sia statico che non statico. Se il bean è un session bean l’observer method deve essere un business method dell’EJB o un metodo statico della bean class. Possono coesistere più observer methods aventi come parametri gli stessi event types e bindings. Un bean può dichiarare più di un observe method.
Ogni observer method deve avere un event parameter esattamente dello stesso tipo di quello che esso osserva. In fase di ricerca di observer methods per un dato evento il container tiene in considerazione il type e i bindings dell’event parameter.
Se l’event parameter non dichiara esplicitamente alcun tipo di binding, il metodo observer osserva gli eventi senza bindings.
Se il type dell’event parameter contiene type variables e/o wildcards, il container scatena una eccezione di tipo javax.inject.DefinitionException in fase di deployment.
Un observer method può essere dichiarato annotando il parametro con @javax.event.Observes. Esempio:
public void afterLogin(@Observes LoggedInEvent event) { ... }
Se un metodo ha più di un parametro annotato con @Observes, il container scatena una eccezione di tipo javax.inject.DefinitionException in fase di deployment. Nel caso di bean definiti all’interno del file bean.xml, gli observer method possono essere dichiarati tramite il tag . Tornando all’esempio precedente, la dichiarazione XML sarebbe la seguente:
Oltre all’event parameter gli observer methods possono dichiarare altri parametri, i quali possono dichiarare dei bindings. Il container invoca il metodo Manager.getInstanceToInject() per determinare un valore per ciascun parametro di un observer method e quindi invoca quest’ultimo passandogli tali valori.
Per ogni observer method di un enabled bean, il container si occupa di fornire e registrare un’adeguata implementazione dell’interfaccia Observer, la quale delega le notifiche degli eventi all’observer method stesso.
Il metodo notify() dell’implementazione di Observer per un determinato observer method invoca o l’observer method immediatamente o in maniera asincrona, oppure registra l’observer method di osservazione per una successiva invocazione durante la fase di completamento di una transazione, utilizzando una sincronizzazione JTA (questo nel caso in cui l’observer method sia di tipo transazionale). Il container non è tenuto a garantire il rilascio di eventi asincroni nel caso di uno shutdown del server o di un caso di failure più generico.
Per invocare un observer method, il container deve passare l’event object all’event parameter e l’oggetto restituito da Manager.getInstanceToInject () a ciascuno degli altri parametri.
Se un observer method è statico, allora il container può invocarlo direttamente. Altrimenti esso deve:
- ottenere il bean object che più specializza quello dichiarato dall’observer method;
- ottenere il context object mediante invocazione del metodo Manager.getContext(), passandogli lo scope del bean;
- ottenere un’istanza del bean invocando il metodo Context.get();
- se l’invocazione di Context.get() restituisce un valore diverso da null deve invocare l’observer method dall’istanza ottenuta.
L’interfaccia Event
Per gli eventi Web Beans fornisce anche una interfaccia, javax.event.Event. Può essere iniettata tramite il binding @javax.event.Fires. Esempio:
@Fires Event loggedInEvent;
È possibile anche specificare ulteriori binding nel punto di injection. Esempio:
@Fires @Admin Event loggedInEvent;
L’interfaccia Event fornisce un metodo per scatenare un evento di un tipo specifico e un metodo per registrare gli observer per eventi dello stesso tipo:
public interface Event { public void fire(T event, Annotation... bindings); public void observe(Observer observer, Annotation... bindings); }
Il primo parametro del metodo fire() è l’event object. Gli altri sono gli event bindings. Il primo parametro del metodo observe() è l’observer object. Gli altri sono gli observed event bindings. Se due istanze dello stesso binding type sono passate a fire() o a observe(), viene scatenata una eccezione di tipo javax.inject.DuplicateBindingTypeException. Se un’istanza che non è una annotation viene passata a fire() o a observe(), viene scatenata una eccezione di tipo java.lang.IllegalArgumentException.
L’annotation @Fires o il tag possono essere applicati in qualsiasi punto di injection di Event type. Se il tipo del punto di injection non è di tipo Event, oppure se non viene specificato alcun parametro concreto, o se il type parameter contiene type variabiles o wildcards, il container lancia una eccezione di tipo javax.inject.DefinitionException in fase di deployment.
Ogni volta che compare l’annotation @Fires in un punto di injection, esiste un bean implicito avente:
- lo stesso bean type e gli stessi bindings presenti nel punto di injection;
- deployment type @Standard;
- scope @Dependent;
- nessun bean name;
- un’implementazione fornita automaticamente dal container.
Il metodo fire() dell’implementazione di Event fornita deve invocare il metodo Manager.fireEvent(), passando come parametri l’event object passato al metodo Event.fire(), tutti i binding dichiarati al punto di injection (tranne @Fires) e tutti i binding passati a Event.fire(). L’applicazione può scatenare eventi invocando il metodo fire(). Esempio:
@Fires @LoggedIn Event loggedInEvent; ... if ( user.isAdmin() ) { loggedInEvent.fire( user, new AdminBinding() {} ); } else { loggedInEvent.fire(user); }
Il metodo observe() dell’implementazione di Event fornita deve invocare il metodo Manager.addObserver() passandogli come parametri l’observer object passato come parametro a Event.observe(), tutti i binding dichiarati al punto di injection (tranne @Fires) e tutti i binding passati a Event.observe(). L’applicazione può registrare observer invocando il metodo observe(). Esempio:
@Fires @LoggedIn Event loggedInEvent; ... loggedInEvent.observe( new Observer() { public void notify(User user) { ... } } );
Conclusioni
In questo articolo, con l’illustrazione degli eventi, abbiamo completato la trattazione delle specifiche dei Web Beans. Nel prossimo articolo vedremo un esempio di implementazione concreta e completa di tali specifiche.
Riferimenti
[1] JSR-299 Expert Group, “JSR-299: Context and Dependency Injection for Java”
[2] G. Iozzia, “Web Beans – I parte: Introduzione e specifiche”, MokaByte 138, Marzo 2009
https://www.mokabyte.it/cms/article.run?articleId=2U2-E3P-NNC-6VT_7f000001_10911033_0a4a63e2