Patrón Observer con EJB 3.1

De ChuWiki

Por medio de anotaciones, los EJB pueden suscribirse a ciertos tipos de datos que son producidos por otros EJB. Vamos a ver un ejemplo sencillo. El código completo lo tienes en github.

El dato a observar[editar]

En el ejemplo más sencillo, se observa un dato por su tipo, es decir, podemos observer String, Integer, ... o cualquier clase que nosotros creemos. Por ejemplo, una clase cualquiera ObservedData

package com.chuidiang.examples.observer;

import java.io.Serializable;

public class ObservedData {
   @Override
   public String toString() {
      return "ObservedData [aNumber=" + aNumber + ", aString=" + aString + "]";
   }
   private int aNumber;
   private String aString;
   public int getaNumber() {
      return aNumber;
   }
   public void setaNumber(int aNumber) {
      this.aNumber = aNumber;
   }
   public String getaString() {
      return aString;
   }
   public void setaString(String aString) {
      this.aString = aString;
   }  
}

No es más que una clase con una serie de atributos y los métodos set y get correspondientes. Le hemos añadido además el método toString() para poder sacar fácilmente su contenido por pantalla.

Un único detalle a tener en cuenta es que este patrón no funciona entre módulos/aplicaciones distintas, aunque estén desplegadas en el mismo contenedor de aplicaciones. Sólo funciona dentro del mismo proyecto ear o ejb-jar.

El productor[editar]

Al EJB que produzca este tipo de datos el contenedor de aplicaciones (Jboss, Glassfish o similar) le inyectará una instancia de la clase Event<ObservedData>. Será esta instancia la que usará para disparar un evento cada vez que cree una clase de tipo ObservedData. El código completo de la clase puede ser como este

package com.chuidiang.examples.observer;

import java.util.logging.Logger;

import javax.ejb.Schedule;
import javax.ejb.Singleton;
import javax.enterprise.event.Event;
import javax.inject.Inject;


/**
 * This EJB produces ObservedData instances. 
 * The class AnObserver will receive them.
 * 
 * @author Chuidiang
 */
@Singleton
public class FireObserver {
   private static final Logger LOG = Logger.getLogger(FireObserver.class.getName());
   
   @Inject
   Event<ObservedData> event;
   
   private int aNumber=0;
   
   @Schedule(hour="*",minute="*",second="*/5")
   public void fireEvent(){
      
      ObservedData data = new ObservedData();
      data.setaString("Hello "+aNumber);
      data.setaNumber(aNumber++);
      
      event.fire(data);
      LOG.info("event fired!!");
   }
}

Para el ejemplo, hemos hecho que este EJB sea un Singleton de forma que se pueda arrancar fácilmente y guardar en sus atributos algunos datos, de forma que el ObservedData producido tenga distinto contenido cada vez. Un Stateless o Statefull bean nos dificultaría esta tarea.

Lo único especial que necesitamos para poder producir eventos es la inyección de

   @Inject
   Event<ObservedData> event;

el contenedor de aplicaciones se encargará de inyectarnos ahí una instancia de esa clase, que es la que servirá para avisar a los observadores de los nuevos eventos producidos.

Hemos anotado el método fireEvent() con @Schedule(hour="*",minute="*",second="*/5"), que no es más que una forma de decirle al contenedor que llame a este método cada 5 segundos (todos los segundos divisibles entre 5 de todos los minutos de todas las horas). De esta forma, crearemos un ObservedData y dispararemos el evento correspondiente cada 5 segundos.

Dentro del método fireEvent(), la creación del ObservedData y relleno de sus datos no tiene ningún secreto especial. La gracia está en la llamada a

event.fire(data);

que es la que dispara el evento de que hemos producido ese data. El contenedor, a partir de aquí, se encargará de pasar el dato a los observadores.

El observador[editar]

El código del observador es sencillo, puede ser como este

package com.chuidiang.examples.observer;

import java.util.logging.Logger;

import javax.ejb.Stateless;
import javax.enterprise.event.Observes;

/**
 * An EJB observer.
 * This EJB observes ObservedData data.
 * 
 * @author Chuidiang
 *
 */
@Stateless
public class AnObserver {
   private static Logger LOG = Logger.getLogger(AnObserver.class.getName());

   public void doSomething(@Observes ObservedData data) {
      LOG.info("Arrived "+data);
   }
}

Es un simple Stateless bean al que hemos puesto un método para tratar los datos observados. La única cosa rara de este método es que hemos anotado el parámetro con @Observes. Así que el contenedor de aplicaciones simplemente llamará a este método pasándole el ObservedData que alguien haya creado. La salida de este programa puede ser algo como esto

...
11:17:55,008 INFO  [com.chuidiang.examples.observer.AnObserver] (EJB default - 5) Arrived ObservedData [aNumber=17, aString=Hello 17]
11:17:55,009 INFO  [com.chuidiang.examples.observer.FireObserver] (EJB default - 5) event fired!!
11:18:00,009 INFO  [com.chuidiang.examples.observer.AnObserver] (EJB default - 7) Arrived ObservedData [aNumber=18, aString=Hello 18]
11:18:00,009 INFO  [com.chuidiang.examples.observer.FireObserver] (EJB default - 7) event fired!!
11:18:05,007 INFO  [com.chuidiang.examples.observer.AnObserver] (EJB default - 4) Arrived ObservedData [aNumber=19, aString=Hello 19]
11:18:05,008 INFO  [com.chuidiang.examples.observer.FireObserver] (EJB default - 4) event fired!!
...

Vemos que cada 5 segundos más o menos se dispara el evento y llega el dato. En el log sale antes que ha llegado el dato porque en el código de FireObserver hemos puesto el log de event fired !! después de haber disparado el evento.

Enlaces[editar]