SeamFramework.orgCommunity Documentation

Capitolo 9. Eventi

9.1. Osservatori di eventi
9.2. Produttori di eventi
9.3. Registrare dinamicamente gli osservatori
9.4. Event binding con membri
9.5. Event binding multipli
9.6. Osservatori transazionali

Il sistema di notifica a eventi di Web Beans consente a Web Beans di interagire in maniera totalmente disaccoppiata. I produttori di eventi sollevano eventi che vengono consegnati agli osservatori di eventi tramite il manager Web Bean. Lo schema base può suonare simile al familiare pattern observer/observable, ma ci sono un paio di differenze:

Un metodo osservatore è un metodo di un Web Bean con un parametro annotato @Observes.

public void onAnyDocumentEvent(@Observes Document document) { ... }

Il parametro annotato viene chiamato parametro evento. Il tipo di parametro evento è il tipo evento osservato. I metodi osservatori possono anche specificare dei "selettori", che sono solo istanze di tipi di binding di Web Beans. Quando un tipo di binding viene usato come selettore di eventi viene chiamato tipo binding di evento.

@BindingType

@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Updated { ... }

Specifichiamo i binding di evento del metodo osservatore annotando il parametro evento:

public void afterDocumentUpdate(@Observes @Updated Document document) { ... }

Un metodo osservatore non ha bisogno di specificare alcun binding di evento — in questo caso è interessato a tutti gli eventi di un particolare tipo. Se specifica dei binding di evento, è solamente interessato agli eventi che hanno anche gli stessi binding di evento.

Il metodo osservatore può avere parametri addizionali che vengono iniettati secondo la solita semantica di iniezione del parametro del metodo Web Beans.

public void afterDocumentUpdate(@Observes @Updated Document document, User user) { ... }

Il produttore dell'evento può ottenere tramite iniezione un oggetto notificatore d'evento:

@Observable Event<Document

> documentEvent

L'annotazione @Observable definisce implicitamente un Web Bean con scope @Dependent e tipo di deploy @Standard, con un'implementazione fornita dal manager Web Bean.

Un produttore solleva eventi chiamando il metodo fire() dell'intefaccia Event, passando un oggetto evento:

documentEvent.fire(document);

Un oggetto evento può essere un'istanza di una classe Java che non ha variabili tipo o parametri tipo wildcard. L'evento verrà consegnato ad ogni metodo osservatore che:

Il manager Web Bean chiama semplicemente tutti i metodi osservatori, passando l'oggento evento come valore del parametro evento. Se il metodo osservatore lancia un'eccezione, il manager Web Bean smette di chiamare i metodi osservatori, e l'eccezione viene rilanciata dal metodo fire().

Per specificare un "selettore" il produttore d'evento può passare un'istanza del tipo di binding d'evento al metodo fire():

documentEvent.fire( document, new AnnotationLiteral<Updated

>(){} );

La classe helper AnnotationLiteral rende possibile istanziare inline i tipi di binding, dato che questo risulta difficile da fare in Java.

L'evento verrà consegnato ad ogni metodo osservatore che:

In alternativa gli event binding possono essere specificati annotando il punto di iniezione del notificato d'evento:

@Observable @Updated Event<Document

> documentUpdatedEvent

Quindi ciascun evento sollevato tramite quest'istanza di Event ha annotato l'event binding. L'evento verrà consegnato ad ogni metodo osservatore che:

E' spesso utile registrare un osservatore d'evento in modo dinamico. L'applicazione può implementare l'interfaccia Observer e registrare un'istanza con un notificatore d'evento chiamando il metodo observe().

documentEvent.observe( new Observer<Document

>() { public void notify(Document doc) { ... } } );

I tipi di event binding possono essere specificati dal punto di iniezione del notificatore d'eventi o passando istance del tipo di event binding al metodo observe():

documentEvent.observe( new Observer<Document

>() { public void notify(Document doc) { ... } }, 
                                                new AnnotationLiteral<Updated
>(){} );

Un tipo di event binding può avere membri annotati:

@BindingType

@Target({PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Role {
    RoleType value();
}

Il valore del membro è usato per restringere i messaggi consegnati all'osservatore:

public void adminLoggedIn(@Observes @Role(ADMIN) LoggedIn event) { ... }

I membri del tipo di eventbinding possono essere specificati staticamente dal produttore di eventi tramite annotazioni nel punto di iniezione del notificatore d'evento:

@Observable @Role(ADMIN) Event<LoggedIn

> LoggedInEvent;}}

Alternativamente il valore del membro del tipo di event binding può esserre dinamicamente determinato dal produttore di eventi. Iniziamo scrivendo una sottoclasse astratta di AnnotationLiteral:

abstract class RoleBinding 

    extends AnnotationLiteral<Role
> 
    implements Role {}

Il produttore di eventi passa un'istanza di questa classe a fire():

documentEvent.fire( document, new RoleBinding() { public void value() { return user.getRole(); } } );

I tipi di event binding possono essere combinati, per esempio:

@Observable @Blog Event<Document

> blogEvent;
...
if (document.isBlog()) blogEvent.fire(document, new AnnotationLiteral<Updated
>(){});

Quando si genera un evento, tutti i seguenti metodi osservatori verranno notificati:

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) { ... }}}

Gli osservatori transazionali ricevono notifiche d'evento prima o dopo la fase di completamento della transazione, nella quale l'evento viene sollevato. Per esempio, il seguente metodo osservatore ha bisogno di aggiornare il set di risultati della query memorizzato nel contesto dell'applicazione, ma solo quando hanno successo le transazioni che aggiornano l'albero Category.

public void refreshCategoryTree(@AfterTransactionSuccess @Observes CategoryUpdateEvent event) { ... }

Ci sono tre tipi di osservatori transazionali:

Gli osservatori transazionali sono molto importanti in un modello ad oggetto stateful come Web Beans, poiché lo stato è spesso mantenuto per un tempo più lungo di una singola transazione atomica.

Si immagini di avere cachato un risultato di query JPA nello scope di applicazione:

@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;
    }
    
}

Di tanto in tanto un Product viene creato o cancellato. Quando questo avviene occorre aggiornare il catalogo del Product. Ma si dovrebbe aspettare che la transazione abbia completato con successo prima di eseguire l'aggiornamento!

Il Web Bean che crea o cancella Product può sollevare eventi, per esempio:

@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
>(){});
    }
    
    ...
    
}

E ora Catalog può osservare gli eventi dopo il completamento (con successo) della transazione:

@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);
    }
    
}