Aprovechando JMS: Con Spring 1.2.x’

Muchos desarrolladores han elegido Spring como reemplazo de J2EE para sus proyectos. Mientras que este enfoque es funcional, y por tanto válido, cabe destacar que esa no es la intención inicial del framework, y que tal premisa forma parte del sumario Ten Common Misconceptions About Spring, escrito por Steve Anglin. Spring hace maravillas con el enfoque antes mencionado sí-y sólo sí- el dominio de problema que atiende el desarrollo está perfectamente acotado dentro de los componentes más usados de J2EE; específicamente la combinación capa de negocio con Stateless Session Beans y capa de web con Servlets, JSPs, ambos, o algún framework de web.Lo que Spring no hace
Spring evoluciona constantemente gracias a que es un proyecto por, y para la comunidad de desarrollo de software. Sin embargo, aún existen algunas brechas tecnológicas que salen a la luz al implementar diseños que se satisfagan con componentes J2EE menos "famosos" como JCA, o JMS. Un ejemplo es la propagación de contextos transaccionales remotos, donde el uso de EJBs se vuelve imprescindible. Otro es el Java Message Service, para el cual Spring no provee un reemplazo out-of-the-box.

JMS + Spring
Según el roadmap de Spring2, éste tendrá un soporte bastante robusto y poderoso para todo lo que concierne a JMS. Dichas características son parte de varios trabajos que han realizado contribuyentes de Spring, como LogicBlaze. Para aquellos que no quieren esperar al release final, hay simples recetas de cocina que pueden usar para crear algunos componentes ligeros como Message-Driven POJOs (MDPs), o Queue/Topic Senders. A continuación se muestra como hacer cada uno.

Message-Driven Pojos
Su definición es auto-explicativa. A continuación se muestra un MDP sencillo:

public class MessageListenerPojo implements MessageListener {
private Queue destination = null;
private QueueConnectionFactory connection
Factory = null;
private MessageManager manager = null;
private QueueConnection connection = null;
private QueueReceiver queueReceiver = null;
}

Obviamente, cada una de estas propiedades tiene sus getters y setters, para hacer posible la inyección de algunas de ellas. La primera propiedad es un javax.jms.Destination de tipo Queue. Es el elemento JMS sobre el cual este MDP va a estar escuchando por mensajes. Esta propiedad puede ser descrita en un application context, como se muestra a continuación:

Queue for listening to messages.

jms/MessageQueue

De esta manera podremos inyectarla a nuestra clase en el application context, como se verá mas tarde.

El QueueConnectionFactory es un objeto JNDI "proxeado" mediante un JndiObjectFactoryBean de Spring en un applicationContext, como se muestra a continuación.

ConnectionFactory for JMS
Serverconnections


jms/ConnectionFactory

El MessageManager es el componente responsable por el procesamiento de la información contenida en el mensaje. Este diseño provee separación total de código de extracción y manejo de mensajes, y código de negocio de procesamiento de la información contenida en él. La implementación de este componente es total responsabilidad del programador.

Las propiedades QueueConnection y QueueReceiver se obtienen de métodos del QueueConnectionFactory, por lo cual se inicializan en un método init. Éstos recursos se liberan al destruirse este MDP, mediante el método destroy. A continuación muestro los listados tanto para el init() como para el destroy().
Método init():

public void init() throws JMSException {
conn = connectionFactory.createQueueConnection();
QueueSession session = null;
conn.setClientID(“MY_CLIENT”);
conn.start();
session = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
queueReceiver = session.createReceiver(destination);
queueReceiver.setMessageListener(this);
}

Método destroy():

public void destroy() throws JMSException {
queueReceiver.setMessageListener(null);
queueReceiver.close();
queueReceiver = null;
connection.close();
connection = null;
}

Estos métodos se ejecutan una sola vez, mediante el mecanismo de init/destroy de Spring. La manera de especificar a Spring que el MDP debe inicializarse con init(), y destruirse con destroy(), es estableciendo las propiedades init-method y destroy-method al describir el MDP en un application context, como se muestra a continuación:

class=”org.springhispano.recetario.MessageListenerPojo” >

Observen ahora la interfaz javax.jms.MessageListener. Dicha interfaz firma el método onMessage, que es responsable de ‘despertar’ el MDP cuando llega un mensaje, inyectada en la propiedad ‘destination’. A continuación se muestra la implementación de este método:

public void onMessage(Message msg) {
try {
if (msg.getJMSRedelivered()) {
LOG.error(“Mensaje reentregado. "+ "No se procesará:“ + msg);
}
else {
LOG.debug(“Mensaje nuevo. Se procesará “ + msg);
Object o = ((ObjectMessage) message).getObject();
manager.processMessage(o);
}
msg.acknowledge();
} catch (JMSException jme) { LOG.error(jme);
}

El método onMessage(), como dijimos anteriormente, sólo extrae la información del mensaje. En este caso, estamos extrayendo un mensaje de tipo objeto (usualmente un JavaBean). Noten cómo el método no efectúa ninguna llamada sobre el objeto que extrajimos del mensaje, delegando dicha lógica al manager. Obviamente, este MDP depende de que los recursos J2EE que se están inyectando, existan en el árbol JNDI del application server. Es posible crear MDPs sin que éstos vivan en un app server. Tal enfoque está fuera del alcance de este tutorial.

Spring Queue / Topic Senders
Para que un MDP "despierte" y pueda consumir los mensajes que llegan a la cola que está escuchando, se requiere que algún componente envíe un mensaje a una cola, o un tópico, respectivamente. Spring ofrece mayores facilidades para la tarea del envío de mensajes a los destinos JMS, que los que ofrece para recibirlos y responder a ellos.

La clase que utilizaremos para acceder a los servicios JMS es
org.springframework.jms.core.JmsTemplate. Cabe destacar que se basa en la versión 1.1+ del API de JMS, donde no es importante si el destino es una cola o un tópico. Para los application servers cuya implementación de JMS se base en la versión 1.0.2, se utiliza la clase JmsTemplate102, que está en el mismo paquete.

Siguiendo con las prácticas propuestas por Spring, nuestro MessageSender deberá ser construido usando el par Interfaz / Implementación. La interfaz firmará un método send, que recibirá como argumento el objeto a enviar. Tipificaremos el parámetro con la interfaz java.io.Serializable:

abstract void send(Serializable obj);

La implementación de esta interfaz llamada MessageSenderImpl, tendrá dos propiedades, cada una con su getter y setter. Una de ellas será de tipo JmsTemplate, y es la clase que nos ayudará con el envío de mensajes. La otra variable miembro será de tipo javax.jms.Destination, que es la interfaz que implementan las colas y los tópicos JMS. Esto nos dará la flexibilidad de poder modificar el destino de los mensajes de una cola a un tópico, sin tener que cambiar nuestro código.

public class MessageSenderImpl implements MessageSender {
private JmsTemplate jmsTemplate = null;
private Destination destination = null;

public void setDestination(Destination destination) {
this.destination = destination;
}
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}

A continuación implementamos el método send, de la manera que se muestra a continuación:

public void send(final Serializable serializable) {
jmsTemplate.send(destination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
Message msg = session.createObjectMessage(serializable);
return msg;
}
} );
}

Este método utiliza el JmsTemplate para enviar el mensaje. A su vez, éste recibe como argumentos la cola o tópico destino del mensaje, y una implementación de org.springframework.jms.core.MessageCreator, que se provee como clase anónima. Dicha interfaz la provee Spring a manera de call-back. El MessageCreator obtendrá la sesión JMS desde el JmsTemplate. La bondad de usar esta interfaz es que no tenemos que preocuparnos por posibles JMSExceptions que ocurran durante la construcción del mensaje en session.createObjectMessage(), dado que Spring las manejará y las convertirá en excepciones no-verificadas, justo como trata el resto de las excepciones de infraestructura.

Ahora sólo nos queda describir nuestro MessageSender en el application context de Spring, con el siguiente listado:

Examinemos la primera propiedad. Si queremos hacer que nuestro MessageSender envíe mensajes que sean consumidos por nuestro MDP, basta con inyectarle el mismo destino JMS. La segunda propiedad es la más importante para nuestro MessageSender. El JmsTemplate se describe en el application context con el siguiente listado:

En este sencillo bean se está inyectando otro del tipo javax.jms.ConnectionFactory. Justo como la propiedad ‘destination’ de nuestro MessageSenderImpl, la propiedad connectionFactory puede ser la misma que la descrita en la primera sección del tutorial, si acaso se desea producir mensajes para el MDP que construimos en la primera sección del tutorial.

¿Qué sigue?
El aprovechamiento de lo presentado aquí, obedece a las mismas reglas de diseño donde sea evidente la implementación del patrón ServiceActivator; por ejemplo en la capa de integración de algún sistema, donde se deba tener comunicación con otras plataformas de manera asíncrona.

Lo que hemos implementado en este tutorial tiene la ventaja de ser ligero (como la mayoría de lo que se construye con Spring, o al menos esa es una de las intenciones del framework) y fácil de construir. Otra ventaja es la centralización de la configuración de consumidores y productores de mensajes en un solo modelo, en contraste a los Message-Driven Beans, los cuales son configurados por dos deployment descriptors con gramática distinta, por lo que se tienen dos puntos de configuración que mantener.

Por otro lado, la desventaja más grande de este enfoque es que las sesiones obtenidas del JmsTemplate de Spring no son transactadas; ni aún cuando el ConnectionFactory sea de tipo XA (es decir, que participe en transacciones distribuídas). Esta es un área de oportunidad para el framework, que se atiende y se resuelve en su versión 2.

Mayor información en www.springframework.org.

Conclusión
Como podemos ver, Spring es un complemento perfecto para la plataforma J2EE, y no un reemplazo, como muchos lo consideran. Si bien es cierto que usar todo el stack de J2EE para aplicaciones pequeñas es como utilizar C4 para volar una puerta emparejada, también es cierto que Spring queda corto para muchos requerimientos de aplicaciones empresariales gigantes; sobre todo aquellas con una gruesa capa de integración. Spring y J2EE deben pertenecer a un enfoque híbrido al momento de escoger tecnologías de desarrollo empresarial, y así, alejarnos de concepciones puristas que, a la larga, limitarán la calidad y variedad de soluciones que podemos entregar a nuestros clientes. Ambos son como el pay de queso y la mermelada de zarzamora: separados saben bien... juntos saben mejor :P

Acerca del autor
Jesús Ramos es egresado del ITESM Campus Monterrey, y se especializa en desarrollo de sistemas financieros en plataforma J2EE y Adobe Flex. Creó el sistema de tesorería para Ixe Banco, así como la capa de servicios del portal financiero IxeNet. Actualmente es Ingeniero de Software en Bursatec S.A., donde creó el sistema de administración de seguridad para S.D. Indeval S.A., y donde co-fundó el grupo de usuarios SpringHispano.org.