SeamFramework.orgCommunity Documentation

Capítulo 11. Eventos

11.1. Conteúdo dos eventos
11.2. Observadores de eventos
11.3. Produtores de Eventos
11.4. Métodos observadores condicionais
11.5. Qualificadores de eventos com membros
11.6. Múltiplos qualificadores de eventos
11.7. Observadores transacionais

Injeção de dependência possibilita baixo acoplamento permitindo que a implementação do bean type injetado alterne, seja durante a implantação ou em tempo de execução. Eventos vão mais a frente, permitem que beans interajam absolutamente com nenhuma dependência em tempo de compilação. Os produtores disparam eventos que são entregues a observadores de evento pelo contêiner.

Este esquema básico pode soar como o conhecido padrão observer/observable, mas existem algumas diferenças:

A funcionalidade de notificação de evento do CDI utiliza mais ou menos a mesma abordagem typesafe que já vimos no serviço de injeção de dependência.

O objeto de evento carrega estado do produtor para o consumidor. O objeto do evento é nada mais que uma instância de uma classe Java concreta. (A única restrição é que as classes de evento não podem conter variáveis de tipo). Um evento pode ter qualificadores atribuídos, permitindo que observadores o diferencie de outros eventos do mesmo tipo. Os qualificadores funcionam como seletores de tópico, possibilitando que um observador reduza o conjunto de eventos a observar.

Um qualificador de evento é apenas um qualificador normal, definido com @Qualifier. Aqui vai um exemplo:

@Qualifier

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

Um método observador (observer method) é um método de um bean com um parâmetro anotado com @Observes.

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

O parâmetro anotado é chamado de parâmetro de evento. O tipo do parâmetro de evento é a classe de evento observada, neste caso Document. O parâmetro de evento também pode especificar qualificadores.

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

Um método observador não precisa especificar qualificadores de evento—neste caso ele estará interessado em todos eventos de um tipo específico. Se ele especificar qualificadores, somente estará interessado em eventos que possuem estes qualificadores.

O método observador pode ter parâmetros adicionais, os quais são pontos de injeção:

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

Os produtores disparam eventos utilizando uma instância da interface Event parametrizada. Uma instância desta interface é obtida por injeção:

@Inject @Any Event<Document

> documentEvent;

Um produtor lança eventos chamando o método fire() da interface Event, passando o objeto de evento:

documentEvent.fire(document);

Este evento específico será entregue a todo método observador que:

O contêiner simplesmente chama todos os métodos observadores, passando o objeto de evento como valor do parâmetro de evento. Se algum método observador lançar uma exceção, o contêiner para de chamar os métodos observadores, e a exceção é relançada pelo método fire().

Os qualificadores podem ser aplicados a um evento em uma das seguintes formas:

Especificar os qualificadores no ponto de injeção é muito mais simples:

@Inject @Updated Event<Document

> documentUpdatedEvent;

Em seguida, todos os eventos disparados por essa instância de Event tem o qualificador de evento @Updated. O evento será entregue a cada método observador que:

A desvantagem de anotar o ponto de injeção é que não podemos especificar o qualificador dinamicamente. CDI nos deixa obter uma instância de qualificador ao estender a classe auxiliadora AnnotationLiteral. Deste modo podemos passar o qualificador para o método select() de Event.

documentEvent.select(new AnnotationLiteral<Updated

>(){}).fire(document);

Eventos podem ter vários qualificadores de evento, agregados usando qualquer combinação de anotações no ponto de injeção de Event e passando instâncias qualificadoras ao método select().

Por padrão, se nenhuma instância de um observador existir no contexto atual, o contêiner instanciará o observador para entregar um evento a ele. Este comportamento não é sempre desejável. Podemos querer entregar eventos somente para instâncias do observador que já existam nos contextos atuais.

Um observador condicional é especificado ao adicionar receive = IF_EXISTS na anotação @Observes.

public void refreshOnDocumentUpdate(@Observes(receive = IF_EXISTS) @Updated Document d) { ... }

Um bean com escopo @Dependent não pode ser um observador condicional, uma vez que ele nunca deveria ser chamado!

Um tipo qualificador de evento pode possuir anotações com membros:

@Qualifier

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

O valor do membro é utilizado para reduzir as mensagens entregues ao observador:

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

Os membros do tipo qualificador de evento podem ser especificados estaticamente pelo produtor de evento, por meio de anotações no ponto de injeção do notificador de evento:

@Inject @Role(ADMIN) Event<LoggedIn

> loggedInEvent;

Alternativamente, o valor do membro do tipo qualificador de evento pode ser determinado dinamicamente pelo produtor de evento. Vamos começar escrevendo uma subclasse abstrata de AnnotationLiteral:

abstract class RoleBinding 

   extends AnnotationLiteral<Role
> 
   implements Role {}

O produtor de evento passa uma instância desta classe para select():

documentEvent.select(new RoleBinding() {

   public void value() { return user.getRole(); }
}).fire(document);

Os tipos qualificadores de evento podem ser combinados, por exemplo:

@Inject @Blog Event<Document

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

Os observadores devem satisfazer completamente o tipo do qualificador final do evento. Assuma os seguintes observadores neste exemplo:

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

O único observador a ser notificado será:

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

No entanto, se houver também um observador:

public void afterBlogUpdate(@Observes @Any Document document) { ... }

Este também deveria ser notificado, uma vez que @Any atua como um sinônimo para qualquer de todos qualificadores.

Observadores transacionais recebem notificações de eventos durante, antes ou após a conclusão da transação em que o evento foi disparado. Por exemplo: o seguinte método observador necessita atualizar um conjunto de resultados de uma consulta que está armazenada no contexto da aplicação, mas apenas quando as transações que atualizam a àrvore de Categoryforem concluídas com sucesso:

public void refreshCategoryTree(@Observes(during = AFTER_SUCCESS) CategoryUpdateEvent event) { ... }

Existem cinco tipos de observadores transacionais:

Os observadores transacionais são muito importantes em um modelo de objetos stateful, porque o estado é muitas vezes mantido por mais de uma única transação atômica.

Imagine que fizemos cache do conjunto de resultados de uma consulta JPA no escopo de aplicação:

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

De tempos em tempos, um Product é criado ou excluído. Quando isso ocorre, precisamos atualizar o catálogo de Product. Mas devemos esperar até depois da transação ser concluída com sucesso antes de realizar essa atualização!

O bean que cria e remove Products pode lançar eventos, por exemplo:

@Stateless

public class ProductManager {
   @PersistenceContext EntityManager em;
   @Inject @Any Event<Product
> productEvent;
   public void delete(Product product) {
      em.delete(product);
      productEvent.select(new AnnotationLiteral<Deleted
>(){}).fire(product);
   }
    
   public void persist(Product product) {
      em.persist(product);
      productEvent.select(new AnnotationLiteral<Created
>(){}).fire(product);
   }
   ...
}

E agora Catalog pode observar os eventos após a conclusão com sucesso da transação:

@ApplicationScoped @Singleton

public class Catalog {
   ...
   void addProduct(@Observes(during = AFTER_SUCCESS) @Created Product product) {
      products.add(product);
   }
    
   void removeProduct(@Observes(during = AFTER_SUCCESS) @Deleted Product product) {
      products.remove(product);
   }
}