Chapter 19. Messaging

In this chapter, you will learn about Spring's support for Java Message Service (JMS). JMS defines a set of standard APIs for message-oriented communication (using message-oriented middleware, a.k.a MOM) in the Java EE platform. With JMS, different applications can communicate in a loosely coupled way compared with other remoting technologies such as RMI. However, when using the JMS API to send and receive messages, you have to manage the JMS resources yourself and handle the JMS API's exceptions, which results in many lines of JMS-specific code. Spring simplifies JMS's usage with a template-based approach, just as it does for JDBC. Moreover, Spring enables beans declared in its IoC container to listen for JMS messages and react to them.

Messaging is a very powerful technique for scaling out your application. It allows work that would otherwise overwhelm a service to be queued up. It also encourages a very decoupled architecture. A component, for example, might only consume messages with a single java.util.Map-based key/value pair. This loose contract makes it a viable hub of communication for multiple, disparate systems, without requiring that they share object types.

Today's messaging middleware stacks are very powerful. There are many factors that apply to messaging middleware performs, including how (or if) the messages are persisted, how they are transmitted en route, and how they are made available to the client. Another factor to consider is how IO occurs. Some commercial message queues (and even a few open source ones) use Java NIO when available or, in some cases—such as with JBoss' HornetQ project or the as-yet-unreleased ActiveMQ 6—they use a native asynchronous IO layer to notch up performance to levels you wouldn't think possible. News of HornetQ's having handily bested other message queues in the SPECjms2007 benchmark in February, 2010 has just emerged as of this writing. These kinds of advances can make it possible to transmit hundreds of thousands of messages per second; today's message queues are definitely not your father's message queues!

Some message queues, like Amazon SQS, provide RESTful interfaces. Amazon's SQS interface is proprietary but powerful. It's also very scalable, backed by Amazon's expertise in cloud-readiness. On the other hand, since it is a REST-based API, some of the plumbing required to use it (polling and consuming messages in Java, for example) is left to the developer to write; there is no JMS API. Another aspect to consider is that message queues are only available to Java clients if they only surface a JMS API. A nascent standard, AMQP, aims to provide a language-neutral specification of a message queue, which any language might consume. Apache's ActiveMQ has some support for this. ActiveMQ's a very fast message queue, but a common sentiment is that RabbitMQ (implemented in Erlang) is faster. Because it surfaces an AMQP-compliant interface, any client language can benefit from its speed.

Going the other way, ActiveMQ might still be the better choice precisely because it supports both JMS and AMQP: Java EE and Spring clients can take advantage of the JMS interface, and other languages can interface through AMQP.

Clearly, there are lots of choices. With all this in mind, we can begin to tackle using JMS with Spring.

By the end of this chapter, you will be able to create and access message-based middleware using Spring and JMS. This chapter will also provide you with a working knowledge of messaging in general, which will help you when we discuss Spring Integration. You will also know how to use Spring's JMS support to simplify sending, receiving, and listening for JMS messages.

Sending and Receiving JMS Messages with Spring

Problem

In the Java EE platform, applications often need to communicate using JMS. To send or receive a JMS message, you have to perform the following tasks:

  1. Create a JMS connection factory on a message broker.

  2. Create a JMS destination, which can be either a queue or a topic.

  3. Open a JMS connection from the connection factory.

  4. Obtain a JMS session from the connection.

  5. Send or receive the JMS message with a message producer or consumer.

  6. Handle JMSException, which is a checked exception that must be handled.

  7. Close the JMS session and connection.

As you can see, a lot of coding is required to send or receive a simple JMS message. In fact, most of these tasks are boilerplate and require you to repeat them each time when dealing with JMS.

Solution

Spring offers a template-based solution for simplifying your JMS code. With a JMS template (Spring framework class JmsTemplate), you can send and receive JMS messages with much less code. The template handles the boilerplate tasks for you and also converts the JMS API's JMSException hierarchy into Spring's runtime exception org.springframework.jms.JmsException hierarchy. The translation converts exceptions to a mirrored hierarchy of unchecked exceptions.

In JMS 1.0.2, topics and queues are known as domains and are handled with a different API that is provided for legacy reasons, so you'll find JARs or implementations of the JMS API in different application servers: one for 1.1, and one for 1.0.2. In Spring 3.0, this 1.0.2 support in Spring is considered deprecated.

JMS 1.1 provides a domain-independent API, treating topic and queue as alternative message destinations. To address different JMS APIs, Spring provides two JMS template classes, JmsTemplate and JmsTemplate102, for these two versions of JMS. This chapter will focus on JMS 1.1, which is available for Java EE 1.4 and higher versions.

How It Works

Suppose that you are developing a post office system that includes two subsystems: the front desk subsystem and the back office subsystem. When the front desk receives mail from a citizen, it passes the mail to the back office for categorizing and delivering. At the same time, the front desk subsystem sends a JMS message to the back office subsystem, notifying it of new mail. The mail information is represented by the following class:

package com.apress.springrecipes.post;

public class Mail {

    private String mailId;
    private String country;
    private double weight;

    // Constructors, Getters and Setters
    ...
}

The methods for sending and receiving mail information are defined in the FrontDesk and BackOffice interfaces as follows:

package com.apress.springrecipes.post;

public interface FrontDesk {

    public void sendMail(Mail mail);
}

package com.apress.springrecipes.post;

public interface BackOffice {

    public Mail receiveMail();
}

Before you can send and receive JMS messages, you need to install a JMS message broker. For simplicity's sake, we have chosen Apache ActiveMQ (http://activemq.apache.org/) as our message broker, which is very easy to install and configure. ActiveMQ is an open source message broker that fully supports JMS 1.1.

Note

You can download ActiveMQ (e.g., v5.3.0) from the ActiveMQ web site and extract it to a directory of your choice to complete the installation.

Sending and Receiving Messages Without Spring's Support

First, let's look at how to send and receive JMS messages without Spring's support. The following FrontDeskImpl class sends JMS messages with the JMS API directly.

Note

To send and receive JMS messages to and from a JMS message broker, you have to include the client library for the message broker, as well as the JMS APIs in your classpath. If you are using Maven, add the following dependencies to your classpath.

<dependency>
   <groupId>javax.jms</groupId>
   <artifactId>jms</artifactId>
   <version>1.1</version>
 </dependency>

 <dependency>
   <groupId>org.apache.activemq</groupId>
   <version>5.3.0</version>
   <artifactId>activemq-all</artifactId>
 </dependency>
package com.apress.springrecipes.post;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageProducer;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;

public class FrontDeskImpl implements FrontDesk {

    public void sendMail(Mail mail) {
        ConnectionFactory cf =
            new ActiveMQConnectionFactory("tcp://localhost:61616");
        Destination destination = new ActiveMQQueue("mail.queue");

        Connection conn = null;
        try {
            conn = cf.createConnection();
            Session session =
                conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer producer = session.createProducer(destination);

            MapMessage message = session.createMapMessage();
            message.setString("mailId", mail.getMailId());
            message.setString("country", mail.getCountry());
            message.setDouble("weight", mail.getWeight());
            producer.send(message);

            session.close();
        } catch (JMSException e) {
            throw new RuntimeException(e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (JMSException e) {
                }
            }
        }
    }
}

In the preceding sendMail() method, you first create JMS-specific ConnectionFactory and Destination objects with the classes provided by ActiveMQ. The message broker URL is the default for ActiveMQ if you run it on localhost. In JMS, there are two types of destinations: queue and topic. As explained before, a queue is for the point-to-point communication model, while topic is for the publish-subscribe communication model. Because you are sending JMS messages point to point from front desk to back office, you should use a message queue. You can easily create a topic as a destination using the ActiveMQTopic class.

Next, you have to create a connection, session, and message producer before you can send your message. There are several types of messages defined in the JMS API, including TextMessage, MapMessage, BytesMessage, ObjectMessage, and StreamMessage. MapMessage contains message content in key/value pairs like a map. All of them are interfaces, whose super class is simply Message. In the meantime, you have to handle JMSException, which may be thrown by the JMS API. Finally, you must remember to close the session and connection to release system resources. Every time a JMS connection is closed, all its opened sessions will be closed automatically. So you only have to ensure that the JMS connection is closed properly in the finally block.

On the other hand, the following BackOfficeImpl class receives JMS messages with the JMS API directly:

package com.apress.springrecipes.post;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageConsumer;
import javax.jms.Session;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.command.ActiveMQQueue;

public class BackOfficeImpl implements BackOffice {

    public Mail receiveMail() {
        ConnectionFactory cf =
            new ActiveMQConnectionFactory("tcp://localhost:61616");
        Destination destination = new ActiveMQQueue("mail.queue");

        Connection conn = null;
        try {
            conn = cf.createConnection();
            Session session =
                conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageConsumer consumer = session.createConsumer(destination);

            conn.start();
            MapMessage message = (MapMessage) consumer.receive();
            Mail mail = new Mail();
            mail.setMailId(message.getString("mailId"));
            mail.setCountry(message.getString("country"));
            mail.setWeight(message.getDouble("weight"));
session.close();
            return mail;
        } catch (JMSException e) {
            throw new RuntimeException(e);
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (JMSException e) {
                }
            }
        }
    }
}

Most of the code in this method is similar to that for sending JMS messages, except that you create a message consumer and receive a JMS message from it. Note that we used the connection's start() method here, although we didn't in the FrontDeskImpl example before. When using a Connection to receive messages, you can add listeners to the connection that are invoked on receipt of a message, or you can block synchronously, waiting for a message to arrive. The container has no way of knowing which approach you will take and so it doesn't start polling for messages until you've explicitly called start(). If you add listeners or do any kind of configuration, you do so before you invoke start().

Finally, you create two bean configuration files—one for the front desk subsystem (e.g., beans-front.xml), and one for the back office subsystem (e.g., beans-back.xml)—in the root of the classpath.

<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-3.0.xsd">

    <bean id="frontDesk"
        class="com.apress.springrecipes.post.FrontDeskImpl" />
</beans>

<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-3.0.xsd">

    <bean id="backOffice"
        class="com.apress.springrecipes.post.BackOfficeImpl" />
</beans>

Now, your front desk and back office subsystems are ready to send and receive JMS messages. You must start up your message broker before sending and receiving messages with the following main classes. To run them, first run FrontDeskMain and then run BackOfficeMain in another window or console.

Note

To start ActiveMQ, you just execute one of the ActiveMQ startup scripts for your operating system. The script itself is called activemq.sh or activemq.bat for Unix variants or Windows, respectively, and is located in the bin directory.

package com.apress.springrecipes.post;

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

public class FrontDeskMain {

    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans-front.xml");

        FrontDesk frontDesk = (FrontDesk) context.getBean("frontDesk");
        frontDesk.sendMail(new Mail("1234", "US", 1.5));
    }
}

package com.apress.springrecipes.post;

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

public class BackOfficeMain {

    public static void main(String[] args) {
        ApplicationContext context =
            new ClassPathXmlApplicationContext("beans-back.xml");

        BackOffice backOffice = (BackOffice) context.getBean("backOffice");
        Mail mail = backOffice.receiveMail();
        System.out.println("Mail #" + mail.getMailId() + " received");
    }
}

Note

You're encouraged to use your messaging middleware's reporting functionality. In these examples, we're using ActiveMQ. With the default installation, you can open http://localhost:8161/admin/queueGraph.jsp to see what's happening with mail.queue, the queue used in these examples. Alternatively, ActiveMQ exposes very useful beans and statistics from JMX. Simply run jconsole, and drill down to org.apache.activemq in the MBeans tab.

Sending and Receiving Messages with Spring's JMS Template

Spring offers a JMS template that can significantly simplify your JMS code. To send a JMS message with this template, you simply call the send() method and provide a message destination, as well as a MessageCreator object, which creates the JMS message you are going to send. The MessageCreator object is usually implemented as an anonymous inner class.

package com.apress.springrecipes.post;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

public class FrontDeskImpl implements FrontDesk {

    private JmsTemplate jmsTemplate;
    private Destination destination;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void setDestination(Destination destination) {
        this.destination = destination;
    }

    public void sendMail(final Mail mail) {
        jmsTemplate.send(destination, new MessageCreator() {
            public Message createMessage(Session session) throws JMSException {
                MapMessage message = session.createMapMessage();
                message.setString("mailId", mail.getMailId());
                message.setString("country", mail.getCountry());
                message.setDouble("weight", mail.getWeight());
                return message;
            }
        });
    }
}

Note that an inner class can access only arguments or variables of the enclosing method that are declared as final. The MessageCreator interface declares only a createMessage() method for you to implement. In this method, you create and return your JMS message with the JMS session provided.

A JMS template helps you to obtain and release the JMS connection and session, and it sends the JMS message created by your MessageCreator object. Moreover, it converts the JMS API's JMSException hierarchy into Spring's JMS runtime exception hierarchy, whose base exception class is org.springframework.jms.JmsException. You can catch the JmsException thrown from send and the other send variants and then take action in the catch block if you want.

In the front desk subsystem's bean configuration file, you declare a JMS template that refers to the JMS connection factory for opening connections. Then, you inject this template as well as the message destination into your front desk bean.

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

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

    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
    </bean>

    <bean id="frontDesk"
        class="com.apress.springrecipes.post.FrontDeskImpl">
        <property name="destination" ref="mailDestination" />
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>
</beans>

To receive a JMS message with a JMS template, you call the receive() method by providing a message destination. This method returns a JMS message, javax.jms.Message, whose type is the base JMS message type (that is, an interface), so you have to cast it into proper type before further processing.

package com.apress.springrecipes.post;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.JmsUtils;

public class BackOfficeImpl implements BackOffice {

    private JmsTemplate jmsTemplate;
    private Destination destination;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }
public void setDestination(Destination destination) {
        this.destination = destination;
    }

    public Mail receiveMail() {
        MapMessage message = (MapMessage) jmsTemplate.receive(destination);
        try {
            if (message == null) {
                return null;
            }
            Mail mail = new Mail();
            mail.setMailId(message.getString("mailId"));
            mail.setCountry(message.getString("country"));
            mail.setWeight(message.getDouble("weight"));
            return mail;
        } catch (JMSException e) {
            throw JmsUtils.convertJmsAccessException(e);
        }
    }
}

However, when extracting information from the received MapMessage object, you still have to handle the JMS API's JMSException. This is in stark contrast to the default behavior of the framework, where it automatically maps exceptions for you when invoking methods on the JmsTemplate. To make the type of the exception thrown by this method consistent, you have to make a call to JmsUtils.convertJmsAccessException() to convert the JMS API's JMSException into Spring's JmsException.

In the back office subsystem's bean configuration file, you declare a JMS template and inject it together with the message destination into your back office bean.

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

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

    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="receiveTimeout" value="10000" />
    </bean>
<bean id="backOffice"
        class="com.apress.springrecipes.post.BackOfficeImpl">
        <property name="destination" ref="mailDestination" />
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>
</beans>

Pay special attention to the receiveTimeout (which specifies how long to wait in milliseconds) property of the JMS template. By default, this template will wait for a JMS message at the destination forever, and the calling thread is blocked in the meantime. To avoid waiting for a message so long, you should specify a receive timeout for this template. If there's no message available at the destination in the duration, the JMS template's receive() method will return a null message.

In your applications, the main use of receiving a message might be because you're expecting a response to something or want to check for messages at an interval, handling the messages and then spinning down until the next interval. If you intend to receive messages and respond to them as a service, you're likely going to want to use the message-driven POJO functionality described later in this chapter. There, we discuss a mechanism that will constantly sit and wait for messages, handling them by calling back into your application as the messages arrive.

Sending and Receiving Messages to and from a Default Destination

Instead of specifying a message destination for each JMS template's send() and receive() method call, you can specify a default destination for a JMS template. Then, you will no longer need to inject it into your message sender and receiver beans again.

<beans ...>
    ...
    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        <property name="connectionFactory" ref="connectionFactory" />
           <property name="defaultDestination" ref="mailDestination" />
    </bean>

    <bean id="frontDesk"
        class="com.apress.springrecipes.post.FrontDeskImpl">
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>
</beans>

<beans ...>
    ...
    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        <property name="receiveTimeout" value ="10000"/>
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="defaultDestination" ref="mailDestination" />
    </bean>
<bean id="backOffice"
        class="com.apress.springrecipes.post.BackOfficeImpl">
        <property name="jmsTemplate" ref="jmsTemplate" />
    </bean>
</beans>

With the default destination specified for a JMS template, you can delete the setter method for a message destination from your message sender and receiver classes. Now, when you call the send() and receive() methods, you no longer need to specify a message destination.

package com.apress.springrecipes.post;
...
import org.springframework.jms.core.MessageCreator;

public class FrontDeskImpl implements FrontDesk {
    ...
    public void sendMail(final Mail mail) {
        jmsTemplate.send(new MessageCreator() {
            ...
        });
    }
}

package com.apress.springrecipes.post;
...
import javax.jms.MapMessage;
...

public class BackOfficeImpl implements BackOffice {
    ...
    public Mail receiveMail() {
        MapMessage message = (MapMessage) jmsTemplate.receive();
        ...
    }
}

Instead of specifying an instance of the Destination interface for a JMS template, you can specify the destination name to let the JMS template resolve it for you, so you can delete the Destination object's declaration from both bean configuration files.

<bean id="jmsTemplate"
    class="org.springframework.jms.core.JmsTemplate">
    ...
    <property name="defaultDestinationName" value="mail.queue" />
</bean>

Extending the JmsGatewaySupport Class

Just like your DAO class can extend JdbcDaoSupport to retrieve a JDBC template, your JMS sender and receiver classes can also extend JmsGatewaySupport to retrieve a JMS template. You have the following two options for classes that extend JmsGatewaySupport to create their JMS template:

  • Inject a JMS connection factory for JmsGatewaySupport to create a JMS template on it automatically. However, if you do it this way, you won't be able to configure the details of the JMS template.

  • Inject a JMS template for JmsGatewaySupport that is created and configured by you.

Of them, the second approach is more suitable if you have to configure the JMS template yourself. You can delete the private field jmsTemplate and its setter method from both your sender and receiver classes. When you need access to the JMS template, you just make a call to getJmsTemplate().

package com.apress.springrecipes.post;

import org.springframework.jms.core.support.JmsGatewaySupport;
...

public class FrontDeskImpl extends JmsGatewaySupport implements FrontDesk {
    ...
    public void sendMail(final Mail mail) {
        getJmsTemplate().send(new MessageCreator() {
            ...
        });
    }
}

package com.apress.springrecipes.post;
...

import org.springframework.jms.core.support.JmsGatewaySupport;

public class BackOfficeImpl extends JmsGatewaySupport implements BackOffice {
    public Mail receiveMail() {
        MapMessage message = (MapMessage) getJmsTemplate().receive();
        ...
    }
}

Converting JMS Messages

Problem

Your application receives messages from your message queue but also should transform those messages from the JMS-specific type to a business-specific class.

Solution

Spring provides an implementation of SimpleMessageConvertor to handle the translation of a JMS message received to a business object and the translation of a business object to a JMS message. You can leverage the default or provide your own.

Approach

So far, you have been handling the raw JMS messages by yourself. Spring's JMS template can help you convert JMS messages to and from Java objects using a message converter. By default, the JMS template uses SimpleMessageConverter for converting TextMessage to or from a string, BytesMessage to or from a byte array, MapMessage to or from a map, and ObjectMessage to or from a serializable object. For your front desk and back office classes, you can send and receive a map using the convertAndSend() and receiveAndConvert() methods, and the map will be converted to/from MapMessage.

package com.apress.springrecipes.post;
...
public class FrontDeskImpl extends JmsGatewaySupport implements FrontDesk {
    public void sendMail(Mail mail) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("mailId", mail.getMailId());
        map.put("country", mail.getCountry());
        map.put("weight", mail.getWeight());
        getJmsTemplate().convertAndSend(map);
    }
}

package com.apress.springrecipes.post;
...
public class BackOfficeImpl extends JmsGatewaySupport implements BackOffice {
    public Mail receiveMail() {
        Map map = (Map) getJmsTemplate().receiveAndConvert();
        Mail mail = new Mail();
        mail.setMailId((String) map.get("mailId"));
        mail.setCountry((String) map.get("country"));
        mail.setWeight((Double) map.get("weight"));
        return mail;
    }
}

You can also create a custom message converter by implementing the MessageConverter interface for converting mail objects.

package com.apress.springrecipes.post;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;

public class MailMessageConverter implements MessageConverter {

    public Object fromMessage(Message message) throws JMSException,
            MessageConversionException {
        MapMessage mapMessage = (MapMessage) message;
        Mail mail = new Mail();
        mail.setMailId(mapMessage.getString("mailId"));
        mail.setCountry(mapMessage.getString("country"));
        mail.setWeight(mapMessage.getDouble("weight"));
        return mail;
    }

    public Message toMessage(Object object, Session session) throws JMSException,
            MessageConversionException {
        Mail mail = (Mail) object;
        MapMessage message = session.createMapMessage();
        message.setString("mailId", mail.getMailId());
        message.setString("country", mail.getCountry());
        message.setDouble("weight", mail.getWeight());
        return message;
    }
}

To apply this message converter, you have to declare it in both bean configuration files and inject it into the JMS template.

<beans ...>
    ...
    <bean id="mailMessageConverter"
        class="com.apress.springrecipes.post.MailMessageConverter" />

    <bean id="jmsTemplate"
        class="org.springframework.jms.core.JmsTemplate">
        ...
        <property name="messageConverter" ref="mailMessageConverter" />
    </bean>
</beans>

When you set a message converter for a JMS template explicitly, it will override the default SimpleMessageConverter. Now, you can call the JMS template's convertAndSend() and receiveAndConvert() methods to send and receive mail objects.

package com.apress.springrecipes.post;
...
public class FrontDeskImpl extends JmsGatewaySupport implements FrontDesk {
    public void sendMail(Mail mail) {
        getJmsTemplate().convertAndSend(mail);
    }
}

package com.apress.springrecipes.post;
...
public class BackOfficeImpl extends JmsGatewaySupport implements BackOffice {
    public Mail receiveMail() {
        return (Mail) getJmsTemplate().receiveAndConvert();
    }
}

Managing JMS Transactions

Problem

You want to participate in transactions with JMS so that the receipt and sending of messages are transactional.

Approach

You can use the same strategy as you will everywhere else in Spring: leveraging Spring's many TransactionManager implementations as needed and wiring the behavior into your beans.

Solution

When producing or consuming multiple JMS messages in a single method, if an error occurs in the middle, the JMS messages produced or consumed at the destination may be left in an inconsistent state. You have to surround the method with a transaction to avoid this problem.

In Spring, JMS transaction management is consistent with other data access strategies. For example, you can annotate the methods that require transaction management with the @Transactional annotation.

package com.apress.springrecipes.post;

import org.springframework.jms.core.support.JmsGatewaySupport;
import org.springframework.transaction.annotation.Transactional;
...
public class FrontDeskImpl extends JmsGatewaySupport implements FrontDesk {
@Transactional
    public void sendMail(Mail mail) {
        ...
    }
}

package com.apress.springrecipes.post;

import org.springframework.jms.core.support.JmsGatewaySupport;
import org.springframework.transaction.annotation.Transactional;
...
public class BackOfficeImpl extends JmsGatewaySupport implements BackOffice {

    @Transactional
    public Mail receiveMail() {
        ...
    }
}

Then, in both bean configuration files, you add the <tx:annotation-driven /> element and declare a transaction manager. The corresponding transaction manager for local JMS transactions is JmsTransactionManager, which requires a reference to the JMS connection factory.

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

    <bean id="transactionManager"
        class="org.springframework.jms.connection.JmsTransactionManager">
        <property name="connectionFactory">
            <ref bean="connectionFactory" />
        </property>
    </bean>
</beans>

If you require transaction management across multiple resources, such as a data source and an ORM resource factory, or if you need distributed transaction management, you have to configure JTA transaction in your application server and use JtaTransactionManager. Of course, your JMS connection factory must be XA compliant (i.e., supporting distributed transactions).

Creating Message-Driven POJOs in Spring

Problem

When you call the receive() method on a JMS message consumer to receive a message, the calling thread is blocked until a message is available. The thread can do nothing but wait. This type of message reception is called synchronous reception, because your application must wait for the message to arrive before it can finish its work.

Starting with EJB 2.0, a new kind of EJB component called a message-driven bean (MDB) was introduced for asynchronous reception of JMS messages. An EJB container can listen for JMS messages at a message destination and trigger MDBs to react to these messages so that your application no longer has to wait for messages. In EJB 2.x, besides being a nonabstract, nonfinal public class with a public constructor and no finalize method, an MDB must implement both the javax.ejb.MessageDrivenBean and javax.jms.MessageListener interfaces and override all EJB life cycle methods (ejbCreate and ejbRemove). In EJB 3.0, an MDB can be a POJO that implements the MessageListener interface and is annotated with the @MessageDriven annotation.

Although MDBs can listen for JMS messages, they must be deployed in an EJB container to run. You may prefer to add the same capability to POJOs so that they can listen for JMS messages without an EJB container.

Solution

Spring allows beans declared in its IoC container to listen for JMS messages in the same way as MDBs. Because Spring adds message-listening capabilities to POJOs, they are called message-driven POJOs (MDPs).

How It Works

Suppose that you want to add an electronic board to the post office's back office to display mail information in real time as it arrives from the front desk. As the front desk sends a JMS message along with mail, the back office subsystem can listen for these messages and display them on the electronic board. For better system performance, you should apply the asynchronous JMS reception approach to avoid blocking the thread that receives these JMS messages.

Listening for JMS Messages with Message Listeners

First, you create a message listener to listen for JMS messages. This negates the need for the approach taken in BackOfficeImpl in previous recipes. For example, the following MailListener listens for JMS messages that contain mail information:

package com.apress.springrecipes.post;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

import org.springframework.jms.support.JmsUtils;

public class MailListener implements MessageListener {

    public void onMessage(Message message) {
        MapMessage mapMessage = (MapMessage) message;
        try {
            Mail mail = new Mail();
            mail.setMailId(mapMessage.getString("mailId"));
            mail.setCountry(mapMessage.getString("country"));
            mail.setWeight(mapMessage.getDouble("weight"));
            displayMail(mail);
        } catch (JMSException e) {
            throw JmsUtils.convertJmsAccessException(e);
        }
    }

    private void displayMail(Mail mail) {
        System.out.println("Mail #" + mail.getMailId() + " received");
    }
}

A message listener must implement the javax.jms.MessageListener interface. When a JMS message arrives, the onMessage() method will be called with the message as the method argument. In this sample, you simply display the mail information to the console. Note that when extracting message information from a MapMessage object, you need to handle the JMS API's JMSException. You can make a call to JmsUtils.convertJmsAccessException() to convert it into Spring's runtime exception JmsException.

Next, you have to configure this listener in the back office's bean configuration file. Declaring this listener alone is not enough to listen for JMS messages. You need a message listener container to monitor JMS messages at a message destination and trigger your message listener on message arrival.

<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-3.0.xsd">

    <bean id="connectionFactory"
        class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>
<bean id="mailListener"
        class="com.apress.springrecipes.post.MailListener" />

    <bean
        class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="destinationName" value="mail.queue" />
        <property name="messageListener" ref="mailListener" />
    </bean>
</beans>

Spring provides several types of message listener containers for you to choose from in the org.springframework.jms.listener package, of which SimpleMessageListenerContainer and DefaultMessageListenerContainer are the most commonly used. SimpleMessageListenerContainer is the simplest one that doesn't support transaction. If you have a transaction requirement in receiving messages, you have to use DefaultMessageListenerContainer.

Now, you can start your message listener with the following main class, which starts the Spring IoC container only:

package com.apress.springrecipes.post;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BackOfficeMain {

    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("beans-back.xml");
    }
}

Listening for JMS Messages with POJOs

While a listener that implements the MessageListener interface can listen for messages, so can an arbitrary bean declared in the Spring IoC container. Doing so means that beans are decoupled from the Spring framework interfaces as well as the JMS MessageListener interface. For a method of this bean to be triggered on message arrival, it must accept one of the following types as its sole method argument:

  • Raw JMS message type: For TextMessage, MapMessage, BytesMessage, and ObjectMessage

  • String: For TextMessage only

  • Map: For MapMessage only

  • byte[]: For BytesMessage only

  • Serializable: For ObjectMessage only

For example, to listen for MapMessage, you declare a method that accepts a map as its argument. This listener no longer needs to implement the MessageListener interface.

package com.apress.springrecipes.post;
...
public class MailListener {

    public void displayMail(Map map) {
        Mail mail = new Mail();
        mail.setMailId((String) map.get("mailId"));
        mail.setCountry((String) map.get("country"));
        mail.setWeight((Double) map.get("weight"));
        System.out.println("Mail #" + mail.getMailId() + " received");
    }
}

A POJO is registered to a listener container through a MessageListenerAdapter instance. This adapter implements the MessageListener interface and will delegate message handling to the target bean's method via reflection.

<beans ...>
    ...
    <bean id="mailListener"
        class="com.apress.springrecipes.post.MailListener" />

    <bean id="mailListenerAdapter"
        class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="mailListener" />
        <property name="defaultListenerMethod" value="displayMail" />
    </bean>

    <bean
        class="org.springframework.jms.listener.SimpleMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="destinationName" value="mail.queue" />
        <property name="messageListener" ref="mailListenerAdapter" />
    </bean>
</beans>

You have to set the delegate property of MessageListenerAdapter to your target bean. By default, this adapter will call the method whose name is handleMessage on that bean. If you want to call another method, you can specify it in the defaultListenerMethod property. Finally, notice that you have to register the listener adapter, not the target bean, with the listener container.

Converting JMS Messages

You can also create a message converter for converting mail objects from JMS messages that contain mail information. Because message listeners receive messages only, the method toMessage() will not be called, so you can simply return null for it. However, if you use this message converter for sending messages too, you have to implement this method. The following example reprints the MailMessageConvertor class written earlier:

package com.apress.springrecipes.post;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;

public class MailMessageConverter implements MessageConverter {

    public Object fromMessage(Message message) throws JMSException,
            MessageConversionException {
        MapMessage mapMessage = (MapMessage) message;
        Mail mail = new Mail();
        mail.setMailId(mapMessage.getString("mailId"));
        mail.setCountry(mapMessage.getString("country"));
        mail.setWeight(mapMessage.getDouble("weight"));
        return mail;
    }

    public Message toMessage(Object object, Session session) throws JMSException,
            MessageConversionException {
        ...
    }
}

A message converter should be applied to a listener adapter for it to convert messages into objects before calling your POJO's methods.

<beans ...>
    ...
    <bean id="mailMessageConverter"
        class="com.apress.springrecipes.post.MailMessageConverter" />

    <bean id="mailListenerAdapter"
        class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
        <property name="delegate" ref="mailListener" />
        <property name="defaultListenerMethod" value="displayMail" />
        <property name="messageConverter" ref="mailMessageConverter" />
    </bean>
</beans>

With this message converter, the listener method of your POJO can accept a mail object as the method argument.

package com.apress.springrecipes.post;

public class MailListener {

    public void displayMail(Mail mail) {
        System.out.println("Mail #" + mail.getMailId() + " received");
    }
}

Managing JMS Transactions

As mentioned before, SimpleMessageListenerContainer doesn't support transactions. So, if you need transaction management for your message listener method, you have to use DefaultMessageListenerContainer instead. For local JMS transactions, you can simply enable its sessionTransacted property, and your listener method will run within a local JMS transaction (as opposed to XA transactions).

<bean
    class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="connectionFactory" />
    <property name="destinationName" value="mail.queue" />
    <property name="messageListener" ref="mailListenerAdapter" />
    <property name="sessionTransacted" value="true" />
</bean>

However, if you want your listener to participate in a JTA transaction, you need to declare a JtaTransactionManager instance and inject it into your listener container.

Using Spring's JMS Schema

Spring, from 2.5 and onward, offers a new JMS schema to simplify your JMS listener and listener container configuration. You must add the jms schema definition to the <beans> root element beforehand.

<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-3.0.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.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://localhost:61616" />
    </bean>
<bean id="transactionManager"
        class="org.springframework.jms.connection.JmsTransactionManager">
        <property name="connectionFactory">
            <ref bean="connectionFactory" />
        </property>
    </bean>

    <bean id="mailMessageConverter"
        class="com.apress.springrecipes.post.MailMessageConverter" />

    <bean id="mailListener"
        class="com.apress.springrecipes.post.MailListener" />

    <jms:listener-container
        connection-factory="connectionFactory"
        transaction-manager="transactionManager"
        message-converter="mailMessageConverter">
        <jms:listener
            destination="mail.queue"
            ref="mailListener" method="displayMail" />
    </jms:listener-container>
</beans>

Actually, you don't need to specify the connection-factory attribute for a listener container explicitly if your JMS connection factory's name is connectionFactory, which can be located by default.

Making the Connection

Problem

Throughout this chapter, for the sake of simplicity, we've explored using Spring's JMS support with a very simple instance of org.apache.activemq.ActiveMQConnectionFactory as our connection factory. This isn't the best choice in practice. As with all things, there are performance considerations. In this recipe, we will discuss these considerations. Part of the issue stems from the judicious resource management task the Spring JmsTemplate performs on behalf of the client. The crux of it is that JmsTemplate closes sessions and consumers on each invocation. This means that it tears down all those objects and restores frees the memory. This is "safe," but not performant, as some of the objects created—like Consumers—are meant to be long lived. This behavior stems from the use of the JmsTemplate in EJB-like environments, where typically the application server's Connection Factory is used, and it, internally, provides connection pooling. In this environment, restoring all the objects simply returns it to the pool, which is the desirable behavior.

Solution

There's no "one size fits all" solution to this. You need to weigh the qualities you're looking for and react appropriately.

How It Works

Generally, you want a connection factory that provides pooling and caching of some sort when publishing messages using JmsTemplate. The first place to look for a pooled connection factory might be your application server (if you're using one). It may very well provide one by default.

In the examples in this chapter, we use ActiveMQ in a stand-alone configuration. ActiveMQ, like many vendors, provides a pooled connection factory class alternative (ActiveMQ provides two, actually: one for use consuming messages with a JCA connector and another one for use outside of a JCA container), we can use these instead to handle caching producers and sessions when sending messages. The following configuration pools a connection factory in a stand-alone configuration. It is a drop-in replacement for the previous examples when publishing messages.

<bean id="connectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"
       destroy-method="stop">
    <property name="connectionFactory">
      <bean class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL">
          <value>tcp://localhost:61616</value>
        </property>
      </bean>
    </property>
  </bean>

If you are receiving messages, you could still stand some more efficiency, because the JmsTemplate constructs a new MessageConsumer each time as well. In this situation, you have a few alternatives: use Spring's various *MessageListenerContainer implementations mechanism (MDPs), because it caches consumers correctly, or use Spring's ConnectionFactory implementations. The first implementation, org.springframework.jms.connection.SingleConnectionFactory, returns the same underlying JMS connection each time (which is thread-safe according to the JMS API) and ignores calls to the close() method. Generally, this implementation works well with the JMS 1.0.2 API and the JMS 1.1 API. A much newer alternative is the org.springframework.jms.connection.CachingConnectionFactory. This one works only with the 1.1 API but has a few advantages. First, the obvious advantage is that it provides the ability to cache multiple instances. Second, it caches sessions, MessageProducers, and MessageConsumers. This makes the JmsTemplate a suitable choice for all messaging needs, even if you can't use the MessageListenerContainer.

Summary

This chapter explored Spring's support for JMS: how JMS fits in an architecture and how to use Spring to build message-oriented architectures. You learned how to both produce and consume messages using a message queue. You worked with Active MQ, a reliable open source message queue. Finally, you learned how to build message-driven POJOs.

The next chapter will explore Spring Integration, which is an ESB-like framework for building application integration solutions, similar to Mule ESB and ServiceMix. You will be able to leverage the knowledge gained in this chapter to take your message-oriented applications to new heights with Spring integration.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset