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:

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.(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
[/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.

tags:

About Pascal Alma

Pascal started as an Oracle Developer in 1997 and developed numerous applications with Oracle Designer/Developer and PL/SQL. Since 2001 Pascal becomes more and more active with the development of software at the Java/J2EE platform. Nowadays Pascal is a senior JEE Developer/ Architect and has a lot of experience with several open source initiatives/ frameworks especially within the Enterprise Integration area. Besides these technical skills Pascal is a big Scrum enthusiastic.

7 Responses to Using Spring JMS

  1. acnesiac says:

    Could you post the code for QueueException and BulkMessage ?

  2. acnesiac says:

    Could you post the code for QueueException and BulkMessage ?

  3. EDH says:

    Were did the original code for the QueueBrowser go ? How would you tackle that using jmsTemplate ?

  4. Pascal Alma says:

    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.

  5. Pascal Alma says:

    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/

  6. Pingback: JMS Spring disadvantages | Redstream Blog

  7. Pingback: JMS Spring disadvantages | Redstream Blog