Chapter 15. JMS

15.1. Introduction

Spring provides a JMS abstraction framework that simplifies the use of the JMS API and shields the user from differences between the JMS 1.0.2 and 1.1 APIs.

JMS can be roughly divided into two areas of functionality, production and consumption of messages. In a J2EE environment, the ability to consume messages asynchronously is provided for by message-driven beans while in a standalone application this is provided for by the creation of MessageListeners or ConnectionConsumers. The functionality in JmsTemplate is focused on producing messages. Future releases of Spring will address asynchronous message consumption in a standalone environment.

The package org.springframework.jms.core provides the core functionality for using JMS. It contains JMS template classes that simplifies the use of the JMS by handling the creation and release of resources, much like the JdbcTemplate does for JDBC. The design principal common to Spring template classes is to provide helper methods to perform common operations and for more sophisticated usage, delegate the essence of the processing task to user implemented callback interfaces. The JMS template follows the same design. The classes offer various convenience methods for the sending of messages, consuming a message synchronously, and exposing the JMS session and message producer to the user.

The package org.springframework.jms.support provides JMSException translation functionality. The translation converts the checked JMSException hierarchy to a mirrored hierarchy of unchecked exceptions. If there are any provider specific subclasses of the checked javax.jms.JMSException, this exception is wrapped in the unchecked UncategorizedJmsException. The package org.springframework.jms.support.converter provides a MessageConverter abstraction to convert between Java objects and JMS messages. The package org.springframework.jms.support.destination provides various strategies for managing JMS destinations, such as providing a service locator for destinations stored in JNDI.

Finally, the package org.springframework.jms.connection provides an implementation of the ConnectionFactory suitable for use in standalone applications. It also contains an implementation of Spring's PlatformTransactionManager for JMS. This allows for integration of JMS as a transactional resource into Spring's transaction management mechanisms.

15.2. Domain unification

There are two major releases of the JMS specification, 1.0.2 and 1.1. JMS 1.0.2 defined two types of messaging domains, point-to-point (Queues) and publish/subscribe (Topics). The 1.0.2 API reflected these two messaging domains by providing a parallel class hierarchy for each domain. Consequentially, a client application was domain specific in the use of the JMS API. JMS 1.1 introduced the concept of domain unification that minimized both the functional differences and client API differences between the two domains. As an example of a functional difference that was removed, if you use a JMS 1.1 provider you can transactionally consume a message from one domain and produce a message on the other using the same Session.

The JMS 1.1 specification was released in April 2002 and incorporated as part of J2EE 1.4 in November 2003. As a result, most application servers that are currently in use are only required to support JMS 1.0.2.

15.3. JmsTemplate

Two implementations of the JmsTemplate are provided. The class JmsTemplate uses the JMS 1.1 API and the subclass JmsTemplate102 uses the JMS 1.0.2 API.

Code that uses the JmsTemplate only needs to implement callback interfaces giving them a clearly defined contract. The MessageCreator callback interface creates a message given a Session provided by the calling code in JmsTemplate. In order to allow for more complex usage of the JMS API, the callback SessionCallback provides the user with the JMS session and the callback ProducerCallback exposes a Session and MessageProducer pair.

The JMS API exposes two types of send methods, one that takes delivery mode, priority, and time-to-live as quality of service (QOS) parameters and one that takes no QOS parameters which uses default values. Since there are many send methods in JmsTemplate, the setting of the QOS parameters have been exposed as bean properties to avoid duplication in the number of send methods. Similarly, the timeout value for synchronous receive calls is set using the property setReceiveTimeout.

Some JMS providers allow the setting of default QOS values administratively through the configuration of the ConnectionFactory. This has the effect that a call to MessageProducer's send method send(Destination destination, Message message) will use QOS different default values than those specified in the JMS specification. Therefore, in order to provide consistent management of QOS values, the JmsTemplate must be specifically enabled to use its own QOS values by setting the boolean property isExplicitQosEnabled to true.

15.3.1. ConnectionFactory

The JmsTemplate requires a reference to a ConnectionFactory. The ConnectionFactory is part of the JMS specification and serves as the entry point for working with JMS. It is used by the client application as a factory to create connections with the JMS provider and encapsulates various configuration parameters, many of which are vendor specific such as SSL configuration options.

When using JMS inside an EJB the vendor provides implementations the JMS interfaces so that they can participate in declarative transaction management and perform pooling of connections and session. In order to use this implementation, J2EE containers typically require that you declare a JMS connection factory as a resource-ref inside the EJB or servlet deployment descriptors. To ensure the use of these features with the JmsTemplate inside an EJB, the client application should ensure that it references the managed implementation of the ConnectionFactory.

Spring provides an implementation of the ConnectionFactory interface, SingleConnectionFactory, that will return the same Connection on all createConnection calls and ignore calls to close. This is useful for testing and standalone environments so that the same connection can be used for multiple JmsTemplate calls that may span any number of transactions. SingleConnectionFactory takes a reference to a standard ConnectionFactory that would typically comes from JNDI.

15.3.2. Transaction Management

Spring provides a JmsTransactionManager that manages transactions for a single JMS ConnectionFactory. This allows JMS applications to leverage the managed transaction features of Spring as described in Chapter 7. The JmsTransactionManager binds a Connection/Session pair from the specified ConnectionFactory to the thread. However, in a J2EE environment the ConnectionFactory will pool connections and sessions, so the instances that are bound to the thread depend on the pooling behavior. In a standalone environment, using Spring's SingleConnectionFactory will result in a using a single JMS Connection and each transaction having its own Session. The JmsTemplate can also be used with the JtaTransactionManager and an XA-capable JMS ConnectionFactory for performing distributed transactions.

Reusing code across a managed and unmanaged transactional environment can be confusing when using JMS API to create a Session from a Connection. This is because the JMS API only has only one factory method to create a Session and it requires values for the transaction and acknowledgement modes. In a managed environment, setting these values in the responsibility of the environments transactional infrastructure, so these values are ignored by the vendor's wrapper to the JMS Connection. When using the JmsTemplate in an unmanaged environment you can specify these values though the use of the properties SessionTransacted and SessionAcknowledgeMode. When using a PlatformTransactionManager with JmsTemplate, the template will always be given a transactional JMS Session.

15.3.3. Destination Management

Destinations, like ConnectionFactories, are JMS administered objects that can be stored and retrieved in JNDI. When configuring a Spring application context one can use the JNDI factory class JndiObjectFactoryBean to perform dependency injection on your object's references to JMS destinations. However, often this strategy is cumbersome if there are a large number of destinations in the application or if there are advanced destination management features unique to the JMS provider. Examples of such advanced destination management would be the creation of dynamic destinations or support for a hierarchical namespace of destinations. The JmsTemplate delegates the resolution of a destination name to a JMS destination object to an implementation of the interface DestinationResolver. DynamicDestinationResolver is the default implementation used by JmsTemplate and accommodates resolving dynamic destinations. A JndiDestinationResolver is also provided that acts as a service locator for destinations contained in JNDI and optionally falls back to the behavior contained in DynamicDestinationResolver.

Quite often the destinations used in a JMS application are only known at runtime and therefore can not be administratively created when the application is deployed. This is often because there is shared application logic between interacting system components that create destinations at runtime according to a well known naming convention. Even though the creation of dynamic destinations are not part of the JMS specification, most vendors have provided this functionality. Dynamic destinations are created with a name defined by the user which differentiates them from temporary destinations and are often not registered in JNDI. The API used to create dynamic destinations varies from provider to provider since the properties associated with the destination are vendor specific. However, a simple implementation choice that is sometimes made by vendors is to disregard the warnings in the JMS specification and to use the TopicSession method createTopic(String topicName) or the QueueSession method createQueue(String queueName) to create a new destination with default destination properties. Depending on the vendor implementation, DynamicDestinationResolver may then also create a physical destination instead of only resolving one.

The boolean property PubSubDomain is used to configure the JmsTemplate with knowledge of what JMS domain is being used. By default the value of this property is false, indicating that the point-to-point domain, Queues, will be used. In the 1.0.2 implementation the value of this property determines if the JmsTemplate's send operations will send a message to a Queue or to a Topic. This flag has no effect on send operations for the 1.1 implementation. However, in both implementations, this property determines the behavior of resolving dynamic destination via implementations of DestinationResolver.

You can also configure the JmsTemplate with a default destination via the property DefaultDestination. The default destination will be used with send and receive operations that do not refer to a specific destination.

15.4. Using the JmsTemplate

To get started using the JmsTemplate you need to select either the JMS 1.0.2 implementation JmsTemplate102 or the JMS 1.1 implementation JmsTemplate. Check your JMS provider to determine what version is supported.

15.4.1. Sending a message

The JmsTemplate contains many convenience methods to send a message. There are send methods that specify the destination using a javax.jms.Destination object and those that specify the destination using a string for use in a JNDI lookup. The send method that takes no destination argument uses the default destination. Here is an example that sends a message to a queue using the 1.0.2 implementation.

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;

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

public class JmsQueueSender {

  private JmsTemplate jt;

  private ConnectionFactory connFactory;

  private Queue queue;

  public void simpleSend() {
    jt = new JmsTemplate102(connFactory, false);
    jt.send(queue, new MessageCreator() {
      public Message createMessage(Session session) throws JMSException {
        return session.createTextMessage("hello queue world");
      }
    });
  }

  public void setConnectionFactory(ConnectionFactory cf) {
      connFactory = cf;
  }

  public void setQueue(Queue q) {
      queue = q;
  }

}
      

This example uses the MessageCreator callback to create a text message from the supplied Session object and the JmsTemplate is constructed by passing a reference to a ConnectionFactory and a boolean specifying the messaging domain. A zero argument constructor and a setConnectionFactory/Queue method are also provided and can be used for constructing the instance using a BeanFactory. The method simpleSend modified to send to a topic instead of a queue is shown below

public void simpleSend() {
  jt = new JmsTemplate102(connFactory, true);
  jt.send(topic, new MessageCreator() {
     public Message createMessage(Session session) throws JMSException {
        return session.createTextMessage("hello topic world");
     }
  });
}     
      

When configuring the 1.0.2 in an application context it is important to remember setting the value of the boolean property PubSubDomain property in order to indicate if you want to send to Queues or Topics.

The method send(String destinationName, MessageCreator c) lets you send to a message using the string name of the destination. If these names are registered in JNDI, you should set the DestinationResolver property of the template to an instance of JndiDestinationResolver.

If you created the JmsTemplate and specified a default destination, the send(MessageCreator c) sends a message to that destination.

15.4.2. Synchronous Receiving

While JMS is typically associated with asynchronous processing, it is possible to consume messages synchronously. The overloaded receive methods provide this functionality. During a synchronous receive the calling thread blocks until a message becomes available. This can be a dangerous operation since the calling thread can potentially be blocked indefinitely. The property receiveTimeout specifies how long the receiver should wait before giving up waiting for a message.

15.4.3. Using Message Converters

In order to facilitate the sending of domain model objects the JmsTemplate has various send methods that take a Java object as an argument for a message's data content. The overloaded methods convertAndSend and receiveAndConvert in JmsTemplate delegate the conversion process to an instance of the MessageConverter interface. This interface defines a simple contract to convert between Java objects and JMS messages. The default implementation, SimpleMessageConverter supports conversion between String and TextMessage, byte[] and BytesMesssage, and java.util.Map and MapMessage. By using the converter, you your application code can focus on the business object that is being sent or received via JMS and not bother with the details of how it is represented as a JMS message.

The sandbox currently includes a MapMessageConverter which uses reflection to convert between a JavaBean and a MapMessage. Other popular implementations choices you might implement yourself are Converters that bust an existing XML marshalling packages, such as JAXB, Castor, XMLBeans, or XStream, to create a TextMessage representing the object.

To accommodate the setting of a message's properties, headers, and body that can not be generically encapsulated inside a converter class, the interface MessagePostProcessor gives you access to the message after it has been converted, but before it is sent. The example below shows how to modify a message header and a property after a java.util.Map is converted to a message.

public void sendWithConversion() {
    Map m = new HashMap();
    m.put("Name", "Mark");
    m.put("Age", new Integer(35));
    jt.convertAndSend("testQueue", m, new MessagePostProcessor() {

         public Message postProcessMessage(Message message)
            throws JMSException {
            message.setIntProperty("AccountID", 1234);
            message.setJMSCorrelationID("123-00001");

            return message;
        }
    });
}
      

This results in a message of the form

MapMessage={ 
  Header={ 
    ... standard headers ...
    CorrelationID={123-00001} 
  } 
  Properties={ 
    AccountID={Integer:1234}
  } 
  Fields={ 
    Name={String:Mark} 
    Age={Integer:35} 
  } 
}
      

15.4.4. SessionCallback and ProducerCallback

While the send operations cover many common usage scenarios, there are cases when you want to perform multiple operations on a JMS Session or MessageProducer. The SessionCallback and ProducerCallback expose the JMS Session and Session/MessageProducer pair respectfully. The execute() methods on JmsTemplate execute these callback methods.