EventDispatcher
O EventDispatcher é o serviço do FrameWork utilizado para notificar outras partes do sistema de forma "transparente" quando determinados eventos ocorrem. Por exemplo:
- Evento de Persistência/Exclusão de um Objeto
- Evento de Email Recebido
- Evento de Falha de Conexão
- Etc.
Funcionamento
Registro do Listener
Para escutar os eventos, o "módulo" ou "parte do sistema" que deseja receber as notificações precisa registrar um Listener de eventos no EventDispatcher. Normalmente esse registro é feito uma única vez quando a aplicação se inicia ou faz deploy.
Ao registrar o listener, é preciso passar o(s) ID(s) do(s) evento(s) que o listener deverá ser notificado. Um mesmo listener pode ser utilizado para ouvir diversos IDs. O ID do evento ocorrido será passado como parâmetro para o Listener.
Regras de Funcionamento
- Toda vez que um evento for disparado, o EventDispatcher notificará todos os listeners do evento em uma Thread separada da Thread que disparou os eventos.
- Por ser disparada em uma nova Thread dentro do EventDispatcher temos as seguintes condições:
- O método de disparo de evento nunca gera erro para a classe que disparou o evento. Logo o EventDispatcher não "trava" nem segura a execução da Thread onde o evento ocorreu.
- Erros na execução dos listeners (lançamento de Exceptions) não atrapalha o funcionamento nem causará RollBack na thread principal que gerou o evento. Deixando ela totalmente destacada.
- A execução dos listeners podem ocorrer em paralelo à Thread que notificou o evento. Isso porque as Threads do Listener são iniciadas no momento em que o método de "fire" é chamado.
- A chamada ocorrerá fora de qualquer sessão ou transaction, o listener deverá criar sua própria sessão ou transaction. Qualquer chamada à fachada precisará se autenticação e/ou criação de uma sessão, de acordo com a estrutura do sistema em desenvolvimento.
![]() |
|
![]() |
|
Disparo do Evento
O disparo do evento deve ocorrer quando determinada ação (evento) ocorrer no sistema, conforme esperado pelos listeners. Há duas maneiras de disparar um evento no sistema:
pelo método fire()
O método fire() dispara um evento no exato momento em que ele acontece. Ao chamar este método o EventDipatcher já iniciará uma Thread que procurará pelos listeners desta notificação e começará a notifica-los em seguida.
Neste método devem ser notadas as seguintes situações:
- Por lançar a nova Thread imediatamente, a Thread de notificação (e por consequência os listeners) podem passar a ser executados em paralelo com a Thread que originou o evento.
- Uma vez que a Thread que disparou o evento pode não ter terminado, ela ainda não realizou o commit dos seus objetos no banco, nem em outras funcionalidades do sistema. Por isso os listeners podem não encontrar, ou não enxergar da mesma maneira algum objeto que esteja sendo manipulado na Thread principal.
- Depois que o evento for disparado, os listeners continuarão a ser chamados mesmo que a Thread principal termine em Exception e cause um RollBack dos objetos no banco. Em outras palavras, o EventDispacher não funcionará dentro da Transaction.
Exemplo de disparo de evento de novo documento disponível:
EventDispatcher.fire("EVENT_NEW_DOCUMENT_AVAILABLE", params);
pelo método fireOnCommit()
O método fireOnCommit() resolve o problema de quando os listeners precisam que os objetos alvo do evento já estejam persistidos no banco de dados e acessíveis. Em outras palavras, é preciso que a Thread que lançou o evento já tenha completado e realizado o commit().
Nesses casos o lançamento do Evento é feito da mesma maneira, só que pelo método fireOnCommit(). Quando este método é chamado o EventDispatcher guarda as informações do evento (ID e propriedades) mas não dispara o evento. O evento será disparado quando a transação do container fizer o commit. Desta maneira todos os objetos já estarão persistidos e acessíveis.
Exemplo de disparo de evento de "chegada de e-mail":
EventDispatcher.fireOnCommit("EVENT_MAIL_FETCH", params);
![]() |
|
Configuração de Escopo
É chamado de escopo a mesma ideia da transaction do Java. De modo simplório: um novo escopo é aberto, eventos são registrados para lançamento com o método fireOnCommit(), e no fim o escopo é finalizado com o método endScope(Boolean commit).
Se o escopo for finalizado com commit = true, os eventos serão disparados. Caso commit = false, os eventos serão descartados, pois é considerado como um rollback dos eventos.
= Sub-escopo
O EventDispatcher trata os subescopos. Ou seja, para cada vez que o método beginScope() for chamado sucessivamente um novo sub-escopo é criado. Consequentemente, para cada vez que o endScope() for chamado o escopo é encerrado.
Quando um sub-escopo é encerrado, seus eventos não serão prontamente disparados. Caso o sub-escopo tenha sido encerrado com commit = false, seus eventos serão descartados. Mas caso seja encerrado com commit = true, seus eventos serão transportados para o escopo acima sucessivamente até chegar no escopo principal. Somente no último, os eventos que "foram sobrevivendo" é que serão lançados.
Configuração da Fachada
Note que para o EventDispatcher saber quando uma chamada da Fachada começou e terminou, a fachada precisa ser configurada para avisar o EventDispatcher. Há duas maneiras para realizar essa configuração:
- Através de um Interceptor - Implementar um interceptor para "abraçar" sua fachada e identificar o início e o encerramento do escopo.
- Através da implementação dos métodos do SessionSynchronization. Não é preciso implementar a interface, apenas marcar dois métodos com a annotations certas:
![]() |
Exemplo de Configuração dos Métodos na Fachada |
@AfterBegin
public void afterBegin() throws EJBException, RemoteException {
EventDispatcher.beginScope();
}
@AfterCompletion
public void afterCompletion(boolean committed) throws EJBException, RemoteException {
EventDispatcher.endScope(committed);
}
|
Ao incluir esse dois métodos na fachada o servidor da aplicação notificará o EJB que a sessão começou (antes de chamar o método da fachada) e depois chamará outro avisando que terminou informando se foi dado o commit ou rollback da transação.
![]() |
|
![]() |
|
Uma vez que o evento só será lançado depois que o commit for realizado, o disparo de evento por este método só pode ser realizado quando estamos dentro de uma transaction, ou "dentro da fachada".
Para controlar isso o EventDispatcher só aceitará que o método seja chamado entre a chamada dos métodos EventDispatcher.beginScope() e EventDispatcher.endScope(committed). Lembrando que esses métodos são chamados pelo Interceptor ou pelos métodos configurados no EJB Statefull. Não devem ser chamados em outras partes do código se você estiver utilizando Fachada EJB. Caso esteja controlando o escopo manualmente eles devem ser chamados em locais estratégicos e bem organizados para não deixar escopos abertos pelo sistema.
Caso algum método dentro do CRUD, entre os métodos de .beginScope() e .endScope(), chame novamente a fachada, causando uma chamada consecutiva do .beginScope(), o EventDispatcher "empilha" as chamadas abrindo um novo escopo de eventos. Imagine o seguinte senário:
- .beginScope(); // Abertura do Escopo 1 do EventDispatcher - .fireOnCommit("Event1", null); // Método lança o evento 1 - <nova chamada da fachada> // Execução de código do CRUD que faz a chamada novamente para outro método através da fachada -- .beginScope(); // Ao entrar na fachada o método é chamado novamente, mesmo sendo a mesma Thread o EventDispatcher abre o Escopo 2 de eventos -- .fireOnCommit("Event2", null); // Método lança o evento 2 dentro do escopo 2 -- .endScope(); // Fachada retorna sinalizando que o Escopo 2 foi finalizado, o EventDispatcher fecha o Escopo 2 e volta a registrar tudo no Escopo 1 - .fireOnCommit("Event3", null); // Evento 3 disparado - .endScope(); // Fechamento do primeiro escopo, fim da fachada e retorno para o "actor".
No modelo de código acima, imagine os seguinte cenários:
- Em nenhum momento há exceptions e o retorno ocorre normalmente - Caso nenhum método de fachada retorne uma exception, causando o seu rollback, todos os 3 eventos serão disparados no fechamento do escopo 1.
- O retorno final, no fechamento do escopo 1 é uma exception - Se no fechamento do primeiro escopo aberto tivermos uma exception, nenhum dos eventos é disparado uma vez que todo o procedimento terminou em rollback.
- A segunda chamada da fachada retorna exception, mas a primeira retorna um valor normal - Neste cenário, apenas os eventos 1 e 3 serão disparados. Isso porque quando a segunda chamada da fachada deu rollback seus eventos serão descartados. Porém os eventos do escopo 1 continuam ativos e se ela não der rollback, seus eventos continuam prontos para serem disparados.
Vantagens Do EventDispatcher
As principais vantagens do EventDispatcher são:
- deixa o código mais limpo ao evitar que tenhamos chamadas de métodos de uma parte de código diretamente para outros módulos/serviços diferentes.
- destaca as transações entre partes do código ao iniciar os listeners em threads separadas temos eles totalmente independentes e impedindo que uma parte do código que eventualmente tenha erro atrapalhe o funcionamento de outra.
- permite chamada de módulos desconhecidos ao notificar eventos, outros módulos desconhecidos pelo módulo que lançou o evento, consegue ser chamado para execução instantaneamente à execução de um código.