Using Spring JMS
Last week I discovered a piece of code in our project that didn't use Spring yet. It was the piece of code that used JMS to access some JBoss queues. Since we did use Spring everywhere in our code where it was possible, I decided to rewrite this piece so it also uses Spring (and so it also profits of all the advantages that Spring offers).
The original code looked like this:
-
package net.pascalalma.jms;
-
-
import javax.jms.JMSException;
-
import javax.jms.MessageProducer;
-
import javax.jms.ObjectMessage;
-
import javax.jms.Queue;
-
import javax.jms.QueueBrowser;
-
import javax.jms.QueueConnection;
-
import javax.jms.QueueConnectionFactory;
-
import javax.jms.QueueReceiver;
-
import javax.jms.QueueSession;
-
import javax.naming.InitialContext;
-
import javax.naming.NamingException;
-
-
import org.apache.commons.logging.Log;
-
import org.apache.commons.logging.LogFactory;
-
-
public class QueueBase {
-
-
private static final Log LOG = LogFactory.getLog(QueueBase.class);
-
-
private static QueueConnectionFactory qcf = null;
-
private boolean transacted;
-
private int ackMode;
-
private String queueName;
-
private boolean init = false;
-
protected QueueConnection qc;
-
protected QueueSession qs;
-
protected Queue queue;
-
protected QueueReceiver qr;
-
-
protected MessageProducer mp;
-
-
protected void init() throws QueueException {
-
LOG.info("Queue init started");
-
if (context == null) {
-
-
try {
-
LOG.info("QueueBase: creating InitialContext for queues (see jndi.properties)");
-
qcf = (QueueConnectionFactory) context.lookup("/ConnectionFactory");
-
throw new QueueException(e);
-
}
-
}
-
-
try {
-
queue = (Queue) context.lookup(queueName);
-
qc = qcf.createQueueConnection();
-
qs = qc.createQueueSession(transacted, ackMode);
-
-
assert (queue != null) : "queue is null";
-
assert (qc != null) : "qc is null";
-
assert (qs != null) : "qs is null";
-
-
init = true;
-
throw new QueueException(e);
-
}
-
}
-
-
public QueueBase() {
-
this.init = false;
-
}
-
-
this.queueName = queueName;
-
this.init = false;
-
}
-
-
/**
-
* Constructor
-
*/
-
this.queueName = queueName;
-
this.transacted = transacted;
-
this.ackMode = ackMode;
-
this.init = false;
-
}
-
-
/**
-
* Sends a message to the queue..
-
*
-
* @param msg
-
* @throws QueueException
-
*/
-
public void send(BulkMessage msg) throws QueueException {
-
try {
-
if ((qs != null) && (mp != null)) {
-
ObjectMessage om = qs.createObjectMessage(msg);
-
mp.send(om);
-
} else {
-
LOG.error("Queue is not running / mp==null ....");
-
}
-
} catch (JMSException e) {
-
throw new QueueException(e);
-
}
-
}
-
-
/*
-
* Get / Receive a message from the queue The receive block at most
-
* <timeoutInMilisecs> miliseconds (0=block)
-
*/
-
public ObjectMessage receive(long timeoutInMilisecs) throws QueueException {
-
-
BulkMessage m = null;
-
ObjectMessage om = null;
-
try {
-
if (qr != null) {
-
om = (ObjectMessage) qr.receive(timeoutInMilisecs);
-
if (om != null) {
-
m = (BulkMessage) om.getObject();
-
}
-
} else {
-
throw new QueueException("Queue receiver is null");
-
}
-
} catch (JMSException e) {
-
throw new QueueException(e);
-
}
-
return om;
-
}
-
-
public QueueBrowser getBrowser() throws JMSException, QueueException {
-
return qs.createBrowser(queue);
-
}
-
-
/**
-
*
-
*/
-
public void destroy() {
-
try {
-
if (qs != null) {
-
qs.close();
-
qs = null;
-
}
-
if (qc != null) {
-
qc.stop();
-
qc.close();
-
qc = null;
-
}
-
LOG.error(e);
-
}
-
}
-
-
return queueName;
-
}
-
-
this.queueName = queueName;
-
}
-
-
public int getAckMode() {
-
return ackMode;
-
}
-
-
public void setAckMode(int ackMode) {
-
this.ackMode = ackMode;
-
}
-
-
public Queue getQueue() {
-
return queue;
-
}
-
-
public void setQueue(Queue queue) {
-
this.queue = queue;
-
}
-
-
public boolean isTransacted() {
-
return transacted;
-
}
-
-
public void setTransacted(boolean transacted) {
-
this.transacted = transacted;
-
}
-
-
}
It was doing a JNDI lookup of the queuefactory, created a new session and started and closed it.
After I rewrote it to use Spring it looked like:
-
package nnet.pascalalma.jms;
-
-
import javax.jms.JMSException;
-
import javax.jms.Message;
-
import javax.jms.ObjectMessage;
-
import javax.jms.Session;
-
-
import org.apache.commons.logging.Log;
-
import org.apache.commons.logging.LogFactory;
-
import org.springframework.jms.core.JmsTemplate;
-
import org.springframework.jms.core.MessageCreator;
-
-
public class QueueBaseService {
-
-
private static final Log LOG = LogFactory.getLog(QueueBaseService.class);
-
-
private JmsTemplate jmsTemplate = null;
-
-
public void setJmsTemplate(JmsTemplate jmsTemplate) {
-
this.jmsTemplate = jmsTemplate;
-
}
-
-
public JmsTemplate getJmsTemplate() {
-
return jmsTemplate;
-
}
-
-
public void sendMessage(final BulkMessage bm) {
-
LOG.debug("sendMessage(final BulkMessage bm)");
-
-
jmsTemplate.send( new MessageCreator() {
-
public Message createMessage(Session session) throws JMSException {
-
ObjectMessage om = session.createObjectMessage();
-
om.setObject(bm);
-
return om;
-
}
-
});
-
}
-
-
public ObjectMessage receiveMessage() {
-
return (ObjectMessage)jmsTemplate.receive();
-
-
}
-
-
return jmsTemplate.getDefaultDestinationName();
-
}
-
-
public void setAcknowledgeMode(int mode){
-
jmsTemplate.setSessionAcknowledgeMode( mode);
-
}
-
-
}
And of course some configuration of the queues and JNDI/JMS:
-
<?xml version="1.0" encoding="UTF-8"?>
-
<!DOCTYPE beans SYSTEM "../dtd/spring-beans.dtd">
-
<beans>
-
-
<!-- JNDI properties -->
-
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
-
<property name="environment">
-
<props>
-
<prop key="java.naming.factory.initial">
-
org.jnp.interfaces.NamingContextFactory
-
</prop>
-
<prop key="java.naming.provider.url">
-
jnp://localhost:1099
-
</prop>
-
<prop key="java.naming.factory.url.pkgs">
-
org.jboss.naming:org.jnp.interfaces
-
</prop>
-
</props>
-
</property>
-
</bean>
-
-
<!-- JNDI Connection Factory -->
-
<bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
-
<property name="jndiTemplate">
-
<ref bean="jndiTemplate"/>
-
</property>
-
<property name="jndiName">
-
<value>ConnectionFactory</value>
-
</property>
-
</bean>
-
-
<!-- The default queue, for incoming bulk messages-->
-
<bean id="destination" class="org.springframework.jndi.JndiObjectFactoryBean">
-
<property name="jndiTemplate">
-
<ref bean="jndiTemplate"/>
-
</property>
-
<property name="jndiName">
-
<value>${queue.in}</value>
-
</property>
-
</bean>
-
-
<!-- Queue Access Object, for the default queue -->
-
<bean id="jmsTemplateQueue" class="org.springframework.jms.core.JmsTemplate102">
-
<property name="connectionFactory">
-
<ref bean="jmsConnectionFactory"/>
-
</property>
-
<property name="defaultDestination">
-
<ref bean="destination"/>
-
</property>
-
<property name="receiveTimeout">
-
<value>60000</value>
-
</property>
-
<!-- this value can be overwritten by setting this parameter at business level-->
-
<property name="sessionAcknowledgeModeName">
-
<value>AUTO_ACKNOWLEDGE</value>
-
</property>
-
<property name="sessionTransacted">
-
<value>false</value>
-
</property>
-
</bean>
-
-
<!-- Queue service for the default queue -->
-
<bean id="queueService" class="net.pascalalma.jms.QueueBaseService">
-
<property name="jmsTemplate">
-
<ref bean="jmsTemplateQueue" />
-
</property>
-
</bean>
-
</beans>
So not only does it take less code to achieve the same thing, it is also much less error prone. At least, that was what I would expect, but when I got back in the office one day later, started my machine and ran some unit tests (after synchronizing my code) I was running into an error:
-
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmsConnectionFactory' defined in class path resource [jmsContext.xml]: Initialization of bean failed; nested exception is java.lang.NoSuchFieldError: doPruning
-
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:355)
-
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:226)
-
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:147)
-
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:176)
-
... 33 more
-
Caused by: java.lang.NoSuchFieldError: doPruning
-
at org.jboss.aop.AOPClassPool.<clinit>(AOPClassPool.java:49)
-
at java.lang.Class.forName0(Native Method)
-
at java.lang.Class.forName(Unknown Source)
-
at org.jboss.aop.instrument.InstrumentorFactory.class$(InstrumentorFactory.java:44)
-
at org.jboss.aop.instrument.InstrumentorFactory.<clinit>(InstrumentorFactory.java:44)
-
at org.jboss.aop.AspectManager$2.run(AspectManager.java:316)
-
at java.security.AccessController.doPrivileged(Native Method)
-
at org.jboss.aop.AspectManager.instance(AspectManager.java:261)
-
at org.jboss.aop.AspectManager.instance(AspectManager.java:254)
-
at org.jboss.jms.client.delegate.ClientConnectionFactoryDelegate.<clinit>(ClientConnectionFactoryDelegate.java)
-
at sun.misc.Unsafe.ensureClassInitialized(Native Method)
-
at sun.reflect.UnsafeFieldAccessorFactory.newFieldAccessor(Unknown Source)
-
at sun.reflect.ReflectionFactory.newFieldAccessor(Unknown Source)
-
at java.lang.reflect.Field.acquireFieldAccessor(Unknown Source)
-
at java.lang.reflect.Field.getFieldAccessor(Unknown Source)
-
at java.lang.reflect.Field.getLong(Unknown Source)
-
at java.io.ObjectStreamClass.getDeclaredSUID(Unknown Source)
-
at java.io.ObjectStreamClass.access$600(Unknown Source)
-
at java.io.ObjectStreamClass$2.run(Unknown Source)
-
at java.security.AccessController.doPrivileged(Native Method)
-
at java.io.ObjectStreamClass.<init>(Unknown Source)
-
at java.io.ObjectStreamClass.lookup(Unknown Source)
-
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
-
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
-
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
-
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
-
at java.io.ObjectInputStream.readObject0(Unknown Source
