Spring 2.0's JMS Improvements

With the release of Spring 1.1 the Spring community was given it’s first taste of JMS support. This support included exception translation, message conversion, and a template class much like JdbcTemplate. This support also took care of domain unification between the JMS 1.0.2 and 1.1 specs. The centerpieces of this support are the JmsTemplate class and it’s JMS 1.0.2 counterpart JmsTemplate102.
This support was a great improvement over using the raw JMS APIs to do enterprise messaging. However it did have a shortcoming; the JmsTemplate only supported synchronous reception of messages using the JmsTemplate.receive() methods. This behavior worked well for many people but the vast majority of users of ended up rolling their own implementations of an asynchronous consumer. In short, they wanted what EJB 2 called Message Driven Beans.
But no longer will users do without. With the release of 2.0M1 and the final 2.0 release later, native support for asynchronous reception of JMS messages has been added. The JmsTemplate is still used for sending of messages at his always been, but it has now been joined by subclasses of AbstractMessageListenerContainer such as DefaultMessageListenerContainer, SimpleMessageListenerContainer, and ServerSessionMessageListener.
Let’s take a look at how to use these MessageListenerContainers. The first step is to create a class that can receive the messages. To do this, one must create a class that implements the MessageListener interface.
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
public class ExampleListener implements MessageListener {
public void onMessage(Message message) {
if (message instanceof TextMessage) {
try {
System.out.println(((TextMessage)message).getText());
} catch (JMSException e) {
throw new RuntimeException(e);
}
} else {
throw new IllegalArgumentException(
"Message must be of type TestMessage");
}
}
}
Once you have that, you’ll need a message producer. This code is the same as it was back before Spring 2.0, so if you have code that does this already, it should not require any changes.
import org.springframework.jms.core.JmsTemplate;
public class ExampleProducer {
private JmsTemplate jmsTemplate;
public ExampleProducer(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void sendMessage() {
jmsTemplate.convertAndSend("Example Message");
}
}
Next, you need to configure your context to create a MessageListenerContainer that routes messages to this bean. You’ll notice that I’m using ActiveMQ implementation classes in this example. This just happens to be one of many JMS implementations and happens to be the one that I’m most familiar with.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="messageListener" class="jmsexample.ExampleListener" />
<bean id="messageProducer" class="jmsexample.ExampleProducer">
<constructor-arg ref="jmsTemplate" />
</bean>
<bean id="jmsTemplate"
class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<property name="defaultDestination" ref="destination" />
</bean>
<bean id="destination" class="org.activemq.message.ActiveMQQueue">
<constructor-arg value="jmsExample" />
</bean>
<bean id="listenerContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="destination" />
<property name="messageListener" ref="messageListener" />
</bean>
<bean id="connectionFactory"
class="org.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://localhost:61616" />
</bean>
</beans>
I’m going to skip it for now, but obviously you’ll need to have an MQ started, and a main method that bootstraps your context. I’ve added an archive of the project from this example so that you can see the rest of the code if you need it.
Finally, you just need to run your application and take a look at the output.
One thing to note is that so far we’ve been dealing with asynchronous reception with a single consumer thread. It is possible to multithread to your consumers (remember that you’ll still have to make them stateless or thread-safe) using the concurrent consumers property of the MessageListenerContainer.
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="concurrentConsumers" value="5" />
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="destination" />
<property name="messageListener" ref="messageListener" />
</bean>
One thing I’d like to note (from my own painful experience) is to make sure that you don’t use concurrent consumers with a Topic. Remember that in a JMS topic all messages are delivered to all consumers on a topic. This means that if you have concurrent consumers on a topic, all of them will receive the same message; typically something that you’d want to avoid. However, if you’re using a queue, obviously this would dispatch each new message to the consumers in a round-robin fashion.
So, there you have it. It isn’t very flashy and is probably very similar to something you may have written at some point, but now all you have to do is use it, you don’t have to maintain it. Let me also say that this is just the tip of the iceberg. The MessageListenerContainers have the ability to take part in transactions, use custom threadpools (like the ones provided with an app server) with the new Spring TaskExecutorabstraction, and even expose the native JMS session to the consumer. Each of those things is a topic for another post though.
Marten Deinum says:
Added on October 9th, 2006 at 2:44 amI was trying to download the provided zip file which should contain the code, however it resulted in a nice 404 :-). Can anyone provide me with the zip file, I'm trying to get a feel for the JMS support in Spring 2.0 and need some tutorial example to look at.
Thanks, Marten
alexson says:
Added on November 3rd, 2006 at 6:24 amCan someone provide me the zip file also. Thanks.
Alexson
Puneet says:
Added on December 8th, 2006 at 2:00 amI too can't find the source zip file. JMSExample.zip
-Puneet
Narada says:
Added on January 5th, 2007 at 5:47 amAny possibility of providing the correct location of that zip file?
Antoine Magnier says:
Added on January 31st, 2007 at 10:42 amHas anyone the correct zip file ?
(the current file is corrupted
I have another question ! how do you adapt this sample receiver for receiving messages submited to a topic by another application before the application containing the receiver started ?
best reguard !
thx
Fred says:
Added on February 1st, 2007 at 5:36 amCan someone provide me the zip file also. Thanks.
chris says:
Added on April 20th, 2007 at 6:52 amJust wondering - why won't the blog poster add that zip-file!? I guess the example is not complete without the zip-file, is it!? Thanks.
Richard says:
Added on April 24th, 2007 at 4:05 amWell i dont mean to be rude but i was just wondering why somebody would lead us half the way through and then abandon us. I guess its not worth it without the zip file. Thanks
Ben Hale (blog author) says:
Added on April 24th, 2007 at 2:13 pmThe broken download link has been fixed.
surajz says:
Added on July 5th, 2007 at 10:31 amThanks for the tutorial.
I recently changed my old code to use the springs JmsTemplate.
Yuvaraj says:
Added on August 22nd, 2007 at 11:43 pmsrc.zip file can be added in .classpath file as an entry to avoid the errors…
Lee says:
Added on October 10th, 2007 at 3:14 pmDoes anybody know which jar file contains org.activemq.message.ActiveMQQueue?
I got a ClassNotFound error after launching my Tomcat server.
Thanks in advance.
-Lee
Lee says:
Added on October 11th, 2007 at 10:44 amI finally find them in apache-activemq_4.x.x but their fully qualified path has been changed like this: org.apache.activemq.command.ActiveMQQueue. The connection factory is also changed as: org.apache.activemq.ActiveMQConnectionFactory.
-Lee
Lee says:
Added on October 15th, 2007 at 10:42 amI followed this example and made message producer and consumer working on my Windows desktop. However, the consumer can only receive half of the text messages that the producer sends in a for-loop. Say, the producer sends 100 messages in a loop, the consumer receives only the odd number messages.
Does anyone have an answer on this?
Thanks in advance.
Shanaka says:
Added on October 21st, 2007 at 11:52 pmHi
I used this example. when I run this program i am getting message twice but I am calling sendmessage function once. any one can help me in this issue.
Thanks
jan says:
Added on November 6th, 2007 at 1:23 pmLet's say instead of having a main method bootstrap this, you want to have the message consumer run asynchronously when a message is sent to the queue.
Do you need to set this up as an MBean that you can then wire into Geronimo, or is there a better way of doing it?
cbgcfg says:
Added on November 29th, 2007 at 9:26 amLet's say instead of having a main method bootstrap this, you want to have the message consumer run asynchronously when a message is sent to the queue.
Do you need to set this up as an MBean that you can then wire into Geronimo, or is there a better way of doing it?
redy says:
Added on November 29th, 2007 at 9:29 amI finally find them in apache-activemq_4.x.x but their fully qualified path has been changed like this: org.apache.activemq.command.ActiveMQQueue. The connection factory is also changed as: org.apache.activemq.ActiveMQConnectionFactor
Giannis says:
Added on December 4th, 2007 at 7:42 amInteresting…