Ejb3 Timer Service: scheduling.
0. Índice de contenidos.
- 1. Introducción
- 2. Entorno.
- 3. Creación de un TimerService.
- 4. Levantando el servicio
- 5. Conclusiones
1. Introducción
Las tareas programadas son una realidad en las aplicaciones JEE: tablas que deben historificar, alarmas que se deben generar,… Lo normal puede ser la definición de un proceso batch que se invoca periódicamente mediante el planificador de tareas de Windows y, en UNIX, a través del cron, o de alguna solución comercial tipo control-M.
Ya hemos visto, en adictos, cómo planificar tareas en Jboss de mano de Francisco Javier Martínez Páez, quién me dio, a su vez, la idea sobre éste tutorial.
Ahora vamos a ver cómo planificar una tarea usando la solución estándar de JEE: EJB Timer Services.
El principal beneficio de su uso es que, al formar parte de estandar, no nos ligamos a un servidor de aplicaciones concreto.
EJB3 Time Service permite especificar un método que es invocado automáticamente después de un determinado intervalo de tiempo.
Vamos a ver un ejemplo de definición de un EJB3 Timer Service, que invoca periódicamente, una tarea ficticia, la generación alguna alarma de sistema.
Se da por hecho que el lector conoce lo básico sobre ejb3 y sus anotaciones.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Sobremesa Dell Dimension 6400, 2.13 Ghz, 2 Gb RAM
- Sistema operativo: Windows XP Media center Edition
- JDK 1.6.0_2
- Eclipse 3.3.
- Jboss 4.2.1.GA
3. Creación de un TimerService.
La implementación de un EJB Timer Service es realmente sencilla. Hablamos de EJB3, lo que implica hablar de anotaciones e injección de servicios:
package com.autentia.tutorial.ejb.scheduler; import java.util.Calendar; import java.util.Collection; import java.util.Iterator; import javax.annotation.Resource; import javax.ejb.Stateless; import javax.ejb.Timeout; import javax.ejb.Timer; import javax.ejb.TimerService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Implementación del servicio @local IAlarmScheduler. */ @Stateless public class AlarmScheduler implements IAlarmScheduler { private static final Log log = LogFactory.getLog(AlarmScheduler.class); /** Injección del TimerService */ @Resource TimerService timerService; /** Hora de ejecución: 23 horas */ private static final int START_HOUR = 23; /** Minutos de ejecución: 0 minutos */ private static final int START_MINUTES = 0; /** Segundos de ejecución: 00 */ private static final int START_SECONDS = 0; /** Intervalo de la ejecución: 1440 = 24 horas */ private static final int INTERVAL_IN_MINUTES = 1440; /** * Levanta el servicio */ public void startUpTimer() { log.info("startUpTimer - alarm scheduler service is active."); shutDownTimer(); Calendar initialExpiration = Calendar.getInstance(); initialExpiration.set(Calendar.HOUR_OF_DAY, START_HOUR ); initialExpiration.set(Calendar.MINUTE, START_MINUTES); initialExpiration.set(Calendar.SECOND, START_SECONDS); long intervalDuration = new Integer(INTERVAL_IN_MINUTES).longValue()*60*1000; log.info("startUpTimer - create new timer service at \""+initialExpiration.getTime()+"\", with \""+intervalDuration+"\" interval in milis."); timerService.createTimer(initialExpiration.getTime(),intervalDuration,null); } /** * Para el servicio */ public void shutDownTimer() { Collectiontimers = timerService.getTimers(); log.info("shutDownTimer - existing timers? " + timers); if (timers != null) { for (Iterator iterator = timers.iterator(); iterator.hasNext();) { Timer t = (Timer) iterator.next(); t.cancel(); log.info("shutDownTimer - timer \""+t+"\" canceled."); } } } /** * método callback que se invocará al terminar el intervalo definido */ @Timeout public void execute(Timer timer) { log.info("executing - " + timer.getInfo()); // TODO: implementar la lógica del proceso de alarmas. } }
Explicamos el código:
- Injectamos el TimerService en la línea 23: @Resource TimerService timerService;
- En la línea 46, creamos el temporizador (timerService.createTimer(…);) con dos parámetros: ejecución inicial (las 23:00:00 horas) e intervalo (cada 24 horas).
- El método shutDownTimer() lista todos los temporizadores y acaba con ellos uno a uno. El hecho de parar el servidor de aplicaciones no implica el borrado de los temporizadores, con lo que si el método startUpTimer no invocase al método shutDownTimer antes de crear un nuevo temporizador, se irían acumulando.
- Por último, el método execute está marcado con la anotación @Timeout. Será el método que se invoque a la hora señalada y después de finalizar cada intervalo.
Para poder acceder al servicio de temporizador debemos crear una interfaz, local o remota, en función de nuestras necesidades, que publique al menos el método que crea el temporizador:
package com.autentia.tutorial.ejb.scheduler; import javax.ejb.Local; @Local public interface IAlarmScheduler{ /** starts the timer */ public void startUpTimer(); /** stops all the timers */ public void shutDownTimer(); }
4. Levantando el servicio.
Para levantar el servicio debemos invocar al método startUpTimer de la interfaz local. Pueden existir varias formas de hacerlo, nosotros vamos a invocarlo a través de un servlet de inicialización.
Definimos un servlet en nuestro web.xml, que se cargue en el arranque del servidor:
Initialize com.autentia.tutorial.web.servlets.InitAppServlet 1
El código del servlet de inicialización que implementa la llamada al servicio local vendría a ser el siguiente:
package com.autentia.tutorial.web.servlets; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.autentia.tutorial.ejb.scheduler.AlarmScheduler; import com.autentia.tutorial.ejb.scheduler.IAlarmScheduler; /** * A servlet that will be called the very first when Application Server is started. */ public class InitAppServlet extends HttpServlet { private static final long serialVersionUID = 7040095709523857004L; private static final Log log = LogFactory.getLog(InitAppServlet.class); private static final String jndiPrefix = ""; IAlarmScheduler alarmScheduler; private static final InitialContext ctx; static { try { ctx = new InitialContext(); } catch (NamingException e) { log.fatal("It is not possible to create a new InitialContext.", e); throw new RuntimeException(e); } } public void init() throws ServletException { log.info("InitAppServlet - init"); try { alarmScheduler = (IAlarmScheduler) ctx.lookup(jndiPrefix + AlarmScheduler.class.getSimpleName() + "/local"); } catch (NamingException e) { log.error("InitAppServlet - NamingException",e); } log.info("InitAppServlet - starting alarm scheduling notification."); // start up alarm scheduler alarmScheduler.startUpTimer(); } protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException { // do nothing, only for initialization purposes } }
Solo voy a hacer hincapié en la línea 53, la invocación al método de inicialización del Timer Service.
Doy por hecho que el resto del código se entiende (la variable jndiPrefix contendría el «nombre_de_ear.ear/» en el caso de desplegar la aplicación bajo un ear), y que tenemos definidas las propiedades de acceso al servicio vía jndi en un fichero de recursos (jndi.properties), visible desde el classLoader de la aplicación web:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=localhost:1099
Arrancando la aplicación en nuestro jboss:
-
el EJB Timer Service: debe estar correctamente registrado:
-
y debemos tenerlo activo… «esperando que den las 23:00:00 horas».
5. Conclusiones.
Perfecto para planificar tareas programadas con un coste mínimo, puesto que delegamos su ejecución al contenedor de ejb’s del servidor de aplicaciones.
Por ahora no tenemos una herramienta con una interfaz gráfica de administración de dichas tareas, con lo que si el volumen de tareas es elevado quizás sea un poco tedioso administrarlas.
En función de nuestras necesidades, debemos ser nosotros los que decidamos si utilizamos el estándar o acudimos a aplicaciones de terceros para su gestión.
Un saludo.
Muy bueno, me ha servido mucho Gracias