Saturday, 31 December 2011

Getting started with Spring JMS integration

Here is a quick introduction on how to get started with Spring JMS integration. We are using an example of asynchronous email system to understand the concepts which is a common case of Spring-JMS integration.

In our example, our application will be generating email objects. Instead of sending them to a mail server directly, it will send them to a jms broker for it to be placed in a queue. Our receiver will pick this message from the queue and do the actual job of sending the email through a mail server.

Let us start setting up pieces of our example one by one. The entire source code used in this example can be downloaded in form of a maven project from this location. SpringSource Tool Suite was used to develop this example.

1) ActiveMQ
Before we can start our producers/receivers, we need to start our jms broker first. We will using ActiveMQ for our example. Head over to ActiveMQ site and download one of the final releases. Unzip the download, go to bin directory and start ActiveMQ by running activemq.bat on Windows system (or activemq for *nix systems).When you start ActiveMQ, you will see output similar to that shown below. Pay particular attention to formatted line. This is the broker url and in this case it says that ActiveMQ is listening for connections on port 61616 on my local machine.


 INFO | For help or more information please see: http://activemq.apache.org/
 INFO | Listening for connections at: tcp://27411:61616
 INFO | Connector openwire Started
 INFO | ActiveMQ JMS Message Broker (localhost, ID:27411-51085-1325482616565-0:1) started
 INFO | jetty-7.1.6.v20100715
 INFO | ActiveMQ WebConsole initialized.
 INFO | Initializing Spring FrameworkServlet 'dispatcher'
 INFO | ActiveMQ Console at http://0.0.0.0:8161/admin
 INFO | ActiveMQ Web Demos at http://0.0.0.0:8161/demo
 INFO | RESTful file access application at http://0.0.0.0:8161/fileserver
 INFO | Started SelectChannelConnector@0.0.0.0:8161

Head over to http://localhost:8161/admin/ to access the admin console. Click on "Queues" and add a queue by entering "email.queue" in "Queue Name" textbox and clicking on "Create" button.

2) Message Generator (JMS Producer)
Our main application would be the source of the messages.  Given below is the spring bean configuration file for the producer

<beans>
    <bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>
    <bean id="emailQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="email.queue" />
    </bean>
    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
    </bean>    
</beans>

The beans are pretty much self explanatory.
"connectionFactory" : A factory that provides connection to the JMS broker. Pay attention to "brokerURL" property which points to the host:port combination pointed out earlier in ActiveMQ section.
"emailQueue": This bean represents the queue to which we want to send our Email message. Pay attention to the constructor-arg which specifies the queue name.
"jmsTemplate": This bean provider the Spring framework helper class that simplifies JMS access code.

The java code  which shows how to send Email messages to jms broker is shown below. The sample code uses jmsTemplate to send 5 Email objects to JMS Broker to be placed in the queue specified by "emailQueue" bean.

    ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        JmsTemplate jmsTemplate = ctx.getBean("jmsTemplate", JmsTemplate.class);
        Destination emailQueue = ctx.getBean("emailQueue", Destination.class);
        for (int i = 0; i < 5; i++) {
            final int j=i;
            jmsTemplate.send(emailQueue, new MessageCreator() {
                public Message createMessage(Session session)
                        throws JMSException {
                    Email email = new Email();
                    email.setToAddress("test@test.com");
                    email.setEmailSubject("Test Subject-"+j);
                    email.setEmailBody("Test Body-"+j);
                    return session.createObjectMessage(email);
                }
            });
        }


3) Synchronous Message Receiver

Given below is the code that implements a JMS Receiver that pulls the Email Messages that have been put in the queue by our sender above. Notice how this uses the same Spring configuration file used by our sender. Receiver loops continually checking for new messages. JMSTemplate.receive method will block till a message is available in the Queue. Also notice that we will encounter javax.jms.JMSException while reading the Email object from JMS message. To convert this into one of the Spring's unchecked exception, we are using Spring's JmsUtils.

        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        JmsTemplate jmsTemplate = ctx.getBean("jmsTemplate", JmsTemplate.class);
        Destination emailQueue = ctx.getBean("emailQueue", Destination.class);
        for (;;) {// jmsTemplate.receive will block until a message is available in the Queue
            ObjectMessage msg = (ObjectMessage) jmsTemplate.receive(emailQueue);
            try {
                Email email = (Email) msg.getObject();
                System.out.println(email);
                // Send the email with Java Mail Api
            } catch (javax.jms.JMSException e) {
                // Catch checked JMSException and convert it into one of
                // unchecked org.springframework.jms.JmsException
                throw JmsUtils.convertJmsAccessException(e);
            }
            
            try {
            // Sleep for sometime before hitting the queue again
            Thread.sleep(10000L);
            }catch(InterruptedException ie){
                Thread.currentThread().interrupt();
                break;
            }
        }

4) Asynchronous Message Receiver

We can delegate the responsibility of monitoring the Queue to Spring framework. When a message is received, Spring can invoke any specified method that you have configured. Take a look at the Spring configuration file for this setup below. Notice jms:listener-container element and bean "emailReceiver"configured as a listener in it.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.0.xsd">

    <bean id="connectionFactory" class="org.apache.activemq.spring.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>

    <bean id="emailQueue" class="org.apache.activemq.command.ActiveMQQueue">
        <constructor-arg value="email.queue" />
    </bean>

    <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
    </bean>
    
    <bean id="emailReceiver" class="edu.vpandey.spring.AsyncEmailReceiver"/>
    
    <jms:listener-container connection-factory="connectionFactory">
        <jms:listener destination="email.queue" ref="emailReceiver" method="emailHandler"/>
    </jms:listener-container>
</beans>

Whenever a message arrives in JMS queue "email.queue", method "emailHandler" is invoked on "emailReceiver" bean. Java code for AsycnEmailReceiver is shown below. As you can see it is just a POJO. Also notice the main method which simply loads the Spring config. Spring automatically creates threads that monitor JMS queues(or topics) and invoke configured methods on specified beans.

package edu.vpandey.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import edu.vpandey.spring.model.Email;

public class AsyncEmailReceiver {
    public void emailHandler(Email email) {        
        System.out.println(email);
    }
    
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-asyncReceiver.xml");        
    }
}



Thats the end of this post. Please leave your comments/feedback in comments section. Dont forget to download the code from this location.

5 comments: