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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | 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 * <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; } } 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <?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:
[code]
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.
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.
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.
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.
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
[/code]
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 ?
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: http://blog.redstream.nl/2007/05/07/using-a-jboss-mbean-in-your-web-application/
Pingback: JMS Spring disadvantages | Redstream Blog
Pingback: JMS Spring disadvantages | Redstream Blog