Spring 2.0's JMS Improvements

Ben Hale

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.

package jmsexample;

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.

package jmsexample;

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.

<?xml version="1.0" encoding="UTF-8"?>
<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.

Example Message

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.

<bean id="listenerContainer"
        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.

 

19 responses


  1. I 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


  2. Can someone provide me the zip file also. Thanks.

    Alexson


  3. I too can't find the source zip file. JMSExample.zip

    -Puneet


  4. Any possibility of providing the correct location of that zip file?


  5. Has 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


  6. Can someone provide me the zip file also. Thanks.


  7. Just 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.


  8. Well 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


  9. The broken download link has been fixed.


  10. Thanks for the tutorial.

    I recently changed my old code to use the springs JmsTemplate.


  11. src.zip file can be added in .classpath file as an entry to avoid the errors…


  12. Does anybody know which jar file contains org.activemq.message.ActiveMQQueue?
    I got a ClassNotFound error after launching my Tomcat server.
    Thanks in advance.

    -Lee


  13. I 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


  14. I 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.


  15. Hi

    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


  16. Let'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?


  17. Let'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?


  18. I 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


  19. Interesting…

Leave a Reply