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 InitialContext context = null; 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)"); context = new InitialContext(); qcf = (QueueConnectionFactory) context.lookup("/ConnectionFactory"); } catch (Exception e) { 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; } catch (Exception e) { throw new QueueException(e); } } public QueueBase() { this.init = false; } public QueueBase(String queueName) { this.queueName = queueName; this.init = false; } /** * Constructor */ public QueueBase(String queueName, boolean transacted, int ackMode) throws QueueException { 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 * 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; } } catch (Exception e) { LOG.error(e); } } public String getQueueName() { return queueName; } public void setQueueName(String 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(); } public String getQueueName() { return jmsTemplate.getDefaultDestinationName(); } public void setAcknowledgeMode(int mode){ jmsTemplate.setSessionAcknowledgeMode( mode); } }
And of course some configuration of the queues and JNDI/JMS has to be done.
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.(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.(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.(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.(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) at java.io.ObjectInputStream.defaultReadFields(Unknown Source) at java.io.ObjectInputStream.readSerialData(Unknown Source) at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source) at java.io.ObjectInputStream.readObject(Unknown Source) at java.rmi.MarshalledObject.get(Unknown Source) at org.jnp.interfaces.MarshalledValuePair.get(MarshalledValuePair.java:72) at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:652) at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:587) at javax.naming.InitialContext.lookup(Unknown Source) at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:123) at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:85) at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:121) at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:71) at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106) at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:125) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1003) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:348) ... 36 more
After a short search it appeared two libraries were conflicting: I had both javassist-3.0.jar and javassist-3.2.jar at my classpath. It is only working with the 3.2 version from within Eclipse. If you’re using the older version you will get the error stack . So after solving this final issue the code not only looks better, it is also much easier to maintain.
Could you post the code for QueueException and BulkMessage ?
Were did the original code for the QueueBrowser go ? How would you tackle that using jmsTemplate ?
Acnesiac,
These classes you mention are really pretty straight forward, therefor I didn’t post them in the Blog.
The QueueException is extending the Exception class and the BulkMessage is just a javabean holding some values I wanted to be put on the queue. Hope this helps.
EDH,
The queuebrowser was only used to obtain the number of messages in the queue. I chose another way to tackle that issue, see this post: https://pragmaticintegrator.wordpress.com/2007/05/07/using-a-jboss-mbean-in-your-web-application/