JMS


Asynchronous messaging is important in many enterprise applications. Spring provides support for consuming JMS messages, which both simplifies the programming model (compared to direct use of the JMS API) and provides a consistent basis for access to advanced features such as dynamic destinations.

Note 

Future versions of Spring will provide support for consuming messages comparable to — but simpler than — Message Driven Beans.

Introduction

JMS, the Java Message Service, is a messaging standard that allows applications to communicate with each other reliably and asynchronously. Usually referred to as Message-Oriented Middleware or MOM, messaging middleware had been used extensively in enterprise solutions before the release of the JMS standard in 1999. Similar in spirit to the ODBC standard for database access, the goals of the JMS standardization process were to provide a common API and uniform delivery semantics across different vendors' messaging systems. Areas such as administration, interoperability, and clustering were not addressed by the specification and allow vendors to differentiate themselves.

The asynchronous nature of messaging middleware is the central characteristic that distinguishes it from RPC-based middleware technologies such as RMI/IIOP. Asynchronous behavior is achieved by having a message broker sit between a message producer and message consumer application. JMS supports two categories of messaging, Point-to-Point (P2P) and Publish/Subscribe messaging (Pub/Sub). The true loosely coupled nature of JMS, where applications can still communicate despite not being available at the same time, is an important aspect to consider in deciding to use JMS in your solution.

JMS providers typically provide many language bindings for their products, which reflects the history of using MOM in enterprise application integration solutions across heterogeneous platforms and programming languages. This provides an effective means of interoperability between the .NET, Java, and C/C++ applications. Combined with a way to define the content of messages, for example using XML Schema, JMS can be used directly as an alternative to asynchronous or synchronous Web Services for interoperability solutions. Synchronous request-reply interactions are also possible using JMS by writing synchronous helper classes that are an abstraction on top of the underlying asynchronous message exchange.

There are two major revisions of the JMS specification: JMS 1.0, which was incorporated into J2EE 1.2; and JMS 1.1, incorporated into J2EE 1.4. The 1.1 revision improved the programming model by providing a polymorphic API that could be used easily across both messaging categories, P2P and Pub/Sub. The 1.0 API had modeled these two categories using a repetitive parallel class hierarchy for each domain. This led to code that was difficult to maintain if there was a need to change from using P2P to Pub/Sub delivery. The 1.1 revision also removed functional differences between the two messaging categories such as mixing P2P message consumption and Pub/Sub production within the same JMS transacted session.

The use of JMS is not restricted to an EJB container; it can also be used in web and standalone environments. The EJB container provides the infrastructure for creating message consumers through the use of Message Driven Beans (MDBs). In addition, through the use of XA-capable JMS providers, the EJB container provides declarative and programmatic transaction management for enlistment of a JMS resource in a JTS transaction. A common scenario is to perform database access and message production within a distributed transaction.

Like the JDBC API, the JMS API is relatively low level, requiring a large volume of code to do simple tasks. This repetitive and error-prone code begs to be encapsulated in an abstraction layer. Hence, Spring uses a similar approach to the approach it takes for the JDBC API. The Spring Framework provides a template class that simplifies the use of the JMS API for producing messages concealing the complexity of doing JNDI lookups and working with multiple JMS API objects. It also shields users from the differences between the 1.0 and 1.1 APIs, ensures proper resource handling, and leverages the managed transaction features of Spring enabling JMS publishing applications to enlist JMS in a distributed transaction without the use of an EJB container.

Creating simple message consumer applications outside the EJB container is a straightforward exercise in the use of the JMS API, which can be an appropriate choice in application scenarios that do not require concurrent processing of messages. Infrastructure for concurrent message consumer applications comparable to the functionality offered by MDBs will be provided in future releases of Spring.

Motivation for Spring's JMS Support

The following code shows the standard usage of the JMS 1.1 API for sending a TextMessage. It assumes the JMS ConnectionFactory and Destination have already been obtained using JNDI or other means. (We assume familiarity with core JMS API concepts: Please consult a reference on J2EE if necessary.)

public void testJMS() throws JMSException {       Connection connection = null;   Session session = null;       try {     connection = connectionFactory.createConnection();     session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);     MessageProducer producer = session.createProducer(null);     Message message = session.createTextMessage("hello world");     producer.send(destination, message);       } finally {     if (session != null) {       try {         session.close();       } catch (JMSException ex) {         logger.warn("Failed to close the session", ex);       }     }     if (connection != null) {       try {         connection.close();       } catch (JMSException ex) {         logger.warn("Failed to close the connection", ex);       }     }   } }

If you are using the JMS 1.0 API, the code inside the try block would be changed to the following:

connection = queueConnectionFactory.createQueueConnection(); session = connection.createQueueSession(false, session.AUTO_ACKNOWLEDGE); QueueSender queueSender = session.createSender(null); Message message = session.createTextMessage("hello world"); queueSender.send(queue, message);

As in the case of JDBC, the visibility of intermediate JMS API objects and the addition of correct resource handling introduce a significant amount of code in order to perform the simplest of JMS operations. Adding the retrieval of the connection factory, destination, and configuration of quality-of-service parameters in the JMS session would further increase the length of the code.

JMS Access via a Template

The JmsTemplate class located in the org.springframework.jms.core package implements the core JMS processing tasks. It follows the same design as the JdbcTemplate, providing simple one-liner methods to perform common send and synchronous receive operations, and using callback interfaces for more advanced usage scenarios. As we've noted before, once you master the core Spring concepts, you can leverage them in many areas.

The code shown previously for sending a simple TextMessage now is simply as follows:

 public void testTemplate() {         JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);   jmsTemplate.convertAndSend(destination, "Hello World!");      } 

As you can see, this is much simpler. There is no need to manage resources in application code.

JmsTemplate uses the JMS 1.1 API, and the subclass JmsTemplate102 uses the JMS 1.0 API. The Boolean property PubSubDomain is used to configure the JmsTemplate with knowledge of what messaging category is being used, point-to-point or Pub/Sub. This is important when using the JmsTemplate102. The default value of this property is false, indicating that the point-to-point domain will be used. This flag has no effect on send operations for the 1.1 implementation since the 1.0 API is agnostic to the messaging category.

The JmsTemplate increases the level of abstraction in using JMS by providing overloaded convertAndSend and receiveAndConvert methods, which represent the message data type as a Java object instead of a javax.jms.Message. These methods convert between Java objects and JMS messages by delegating the conversion process to an instance of the MessageConverter interface. This allows your application code to focus on the business object that is being sent or received with JMS and not the details of how to represent the object as a JMS message. In the previous example, the default message converter is used to convert the string "Hello world" to a TextMessage. MessageConverters are covered in greater detail later in the chapter.

Callback Methods

Many of JmsTemplate's methods use callback interfaces to provide greater control over the message creation process and to expose the JMS Session and MessageProducer to the user. The callback interfaces let you control the key aspects of using the raw JMS API while letting the template handle correct resource management of JMS connections and sessions.

Important 

As you should realize, the use of callback methods is consistent with that in Spring's JDBC abstraction, and other Spring libraries such as JNDI and Hibernate and TopLink support. Callbacks are a powerful concept that can be applied to many enterprise (and other) APIs.

Sending and receiving of javax.jms.Messages are performed using JmsTemplate's send and receive methods. These methods use an instance of the callback interface MessageCreator shown here:

public interface MessageCreator {       Message createMessage(Session session) throws JMSException;     }

The following code uses the template method send(Destination, MessageCreator) and an anonymous inner class to implement the MessageCreator callback:

JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory); jmsTemplate.send(    destination,     new MessageCreator() {              public Message createMessage(Session session) throws JMSException {        Message m = session.createTextMessage("Hello World!");        m.setIntProperty("UserID", 46);        m.setJMSCorrelationID("314-61803");        return m;      }    } );

The MessageCreator callback exposes the JMS session object allowing you to create any of the supported JMS message types. Once the message is created, you can set the property and client-assigned header values as you would in standard JMS code.

While the send and convertAndSend methods cover most 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 a Session/MessageProducer pair respectively. The execute() methods on JmsTemplate execute these callback methods. If you are using a JMS 1.0 provider, you will need to downcast MessageProducer to point-to-point or Pub/Sub subclasses to access send and publish methods.

Synchronous Receiving

While JMS is typically associated with asynchronous processing, it is straightforward to consume messages synchronously using the receive method on a MessageConsumer. The overloaded receive and receiveAndConvert methods on JmsTemplate provide this functionality. In addition, the receive methods will also start the JMS connection in order to enable the delivery of messages.

Important 

A synchronous receive will block the calling thread until a message becomes available. This can be a dangerous operation because 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.

Quality of Service Parameters

The raw JMS API has two categories of send methods: one that specifies delivery mode, priority, and time-to-live as quality of service (QOS) parameters; and another with no parameters. The no-parameter send method uses the default QOS values specified by the JMS standard. Because there are many overloaded send methods in JmsTemplate, the QOS parameters have been exposed as bean properties to avoid duplication in the number of send methods. These properties are named DeliveryMode, Priority, and TimeToLive. Similarly, the timeout value for synchronous receive calls is set using the property ReceiveTimeout.

Some JMS providers allow the setting of default QOS values administratively through the configuration of ConnectionFactory or Destination objects that override client-specified values. In order to raise awareness of this issue for configuring consistent management of QOS values, the JmsTemplate must be specifically enabled to use its client-specific QOS values by setting the Boolean property isExplicitQosEnabled to true.

Exception Handling

The JMS API provides a checked exception hierarchy that effectively captures messaging error conditions. Following the use of unchecked exceptions throughout Spring, the JmsTemplate class converts the checked javax.jms.JMSException to the unchecked org.springframework.jms.JmsException. While the analogy with data access and the DAO patterns is not perfect, the use of unchecked exceptions with JMS does facilitate the use of the Strategy pattern to separate the details of a service interface with a particular implementation as seen in high-level user code.

The package org.springframework.jms.support provides JMSException translation functionality. Unlike the case with JDBC, the JMS exception translation is considerably less complex and 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.

ConnectionFactory Management

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 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 or web container, the container provides an implementation of the JMS ConnectionFactory interface that can participate in declarative transaction management and perform pooling of connections and sessions. In order to use this implementation, 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 or servlet, the client application should ensure that it references the managed implementation of the ConnectionFactory.

Spring provides an implementation of the ConnectionFactory interface, SingleConnectionFactory, which returns the same Connection on all createConnection calls and ignores 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 come from JNDI.

Message Converters

As mentioned previously, message converters are used to support JmsTemplate's convertAndSend and receiveAndConvert methods, which represent the JMS message data as a Java object. The MessageConverter interface is in the org.springframework.jms.support.converter package and is defined as

public interface MessageConverter {       Message toMessage(Object object, Session session) throws JMSException,  MessageConversionException;       Object fromMessage(Message message) throws JMSException, MessageConversionException;     }

Spring provides a default implementation, SimpleMessageConverter, which supports conversion between String and TextMessage, byte[] and BytesMesssage, java.util.Map and MapMessage, and Serializable and ObjectMessage. Please note that sending serialized objects via JMS is not a best practice and should be used carefully, if at all, because of the difficulties of keeping producer and consumers synchronized with the same class version.

To accommodate the setting of message content that cannot 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 that follows shows how to modify a message header and a property after a java.util.Map is converted to a JMS message:

Map m = new HashMap(); m.put("Name", "Stewie"); m.put("Age", new Integer(1));     jmsTemplate.convertAndSend(destination, m,      new MessagePostProcessor() {           public Message postProcessMessage(Message message) throws JMSException {         message.setIntProperty("AccountID", 1234);         message.setJMSCorrelationID("314-61803");         return message;     }   } );

This results in a message of the form:

MapMessage={    Header={      ... standard headers ...     CorrelationID={314-61803}    }    Properties={      AccountID={Integer:1234}   }    Fields={      Name={String:Stewie}      Age={Integer:1}    }  } 

We encourage you to develop more sophisticated implementations that can marshal a broader class of Java objects. One implementation option is to leverage popular XML data marshalling toolkits such as JAXB, Castor, XMLBeans, or XStream to create a JMS TextMessage representing the object. The definition of messages exchanged would then leverage standard XML facilities such as XML DTDs or Schemas. Another implementation option would be to use reflection to convert a Java object to a MapMessage. A robust implementation based on MapMessages and reflection requires the use of nested MapMessages, which are not supported by the JMS specification but are a popular vendor extension.

Destination Management

Destinations such as connection factories are JMS-administered objects that can be stored and retrieved in JNDI. When configuring a Spring application context, you can use the JNDI factory class JndiObject FactoryBean to perform dependency injection on your object's references.

<bean  >   <property name="jndiName">     <value>testQueue</value>   </property> </bean>

This can then be used to set a Destination property on the object that will be performing JMS operations. Alternatively, you can also configure the JmsTemplate with a default destination using the property DefaultDestination. The default destination will be used with send and receive methods that do not refer to a specific destination.

However, using Dependency Injection can be cumbersome or inappropriate if there are a large number of destinations or if advanced destination management features are offered by JMS provider. Examples of such advanced destination management would be the creation of dynamic destinations or support for a hierarchical namespace of destinations.

Often the destinations used in a JMS application are known only at runtime and cannot 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. Although the creation of dynamic destinations is not part of the JMS specification, most vendors support it. Dynamic destinations are created with a name defined by the user that differentiates them from JMS temporary destinations and are often not registered in JNDI.

The API used to create dynamic destinations varies from provider to provider because 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 use the TopicSession method createTopic(String topicName) or the QueueSession method createQueue(String queueName) to create a new destination with default destination properties.

The send methods of JmsTemplate that take a string argument for a destination name delegate the resolution of that name to a JMS Destination object using an implementation of the DestinationResolver interface. This interface and related classes are contained in the org.springframework.jms.support.destination package.

public interface DestinationResolver {   Destination resolveDestinationName(Session session,                                       String destinationName,                                           boolean pubSubDomain)                                      throws JMSException; }

Spring provides two implementations of this interface. DynamicDestinationResolver is the default implementation used by JmsTemplate and accommodates resolving dynamic destinations by making calls to a Session's createQueue or createTopic method as described previously. The JmsTemplate property PubSubDomain is used by DestinationResolver to choose which API call to make when resolving a dynamic destination. A JndiDestinationResolver is also provided, which acts as a service locator for destinations contained in JNDI and optionally falls back to the behavior contained in Dynamic DestinationResolver.

Transaction Management

Spring provides a JmsTransactionManager, which manages transactions for a single JMS ConnectionFactory. This allows JMS applications to leverage the managed transaction features of Spring as described in Chapter 6. The JmsTransactionManager binds a Connection / Session pair from the specified ConnectionFactory to the thread. 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.

This transaction strategy will typically be used in combination with SingleConnectionFactory, which uses a single JMS connection for all JMS access to save resources, typically in a standalone application. Each transaction will then use the same JMS Connection but its own JMS 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 has only one factory method to create a Session and it requires values relating to the use of JMS local transactions and acknowledgment modes. JMS local transactions provide the ability to group sending or receiving of multiple messages into a single unit of work. In a managed environment, setting these values is the responsibility of the environment's 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 through the use of the properties SessionTransacted and SessionAcknowledgeMode. When using a PlatformTransactionManager with JmsTemplate, the template will always be given a transactional JMS Session.

JmsGatewaySupport

Spring provides a convenient JmsGatewaySupport class located in the package org.spring framework.jms.core.support, which can be used as a foundation for implementing the Gateway pattern defined in Martin Fowler's Patterns of Enterprise Application Architecture (Addison-Wesley, 2001).

Important 

The Gateway pattern is a wrapper that provides a simple domain-specific API in order to simplify access to external resources that have a complex resource-specific API. Client code that uses the gateway is thereby insulated from the particularities of the resource's API, which would have otherwise obscured the business processing code.

Implementing the Gateway pattern as a business interface provides a valuable layer of indirection allowing one to easily change the implementation to use a different external resource, for example a Web Service. An added benefit is the improved testability of the application because stub or mock implementations can easily be created to reduce the reliance on external resources that may be difficult to configure or access during testing.

The JmsGatewaySupport class creates a JmsTemplate instance when its ConnectionFactory property is set. By overriding the method createJmsTemplate, a custom instance can be created. Alternatively, the gateway can be configured directly with an instance of a JmsTemplate. JmsGatewaySupport implements the InitializingBean interface and delegates the afterPropertiesSet method to the method initGateway so subclasses can implement custom initialization behavior.

In the following extremely simplified example, we consider a scenario in which a customer relationship management application needs to notify a billing application to reissue a new invoice. The business interface is defined as

public interface InvoiceService {       public void reissue(String accountId, Date billingEndDate);     }

The JMS Gateway can then be coded as

public class JmsInvoiceService extends JmsGatewaySupport implements InvoiceService {       public void reissue(String accountId, Date billingEndDate)   {     Map m = new HashMap();     m.put("accountid", accountId);     m.put("date", billingEndDate.toGMTString());     getJmsTemplate().convertAndSend(m);         }       protected void initGateway()   {     getJmsTemplate().convertAndSend("ServiceNotification",                                "JmsInvoiceService Started on " +                                        new Date().toGMTString());   } } 

The following XML configuration segment shows the use of a JmsTemplate instance with a default destination:

<bean  >   <property name="jmsTemplate"><ref bean="jmsTemplate"/></property>     </bean>     <bean  >   <property name="connectionFactory"><ref bean="jmsConnFactoryWrapper"/></property>   <property name="defaultDestination"><ref bean="queue"/></property> </bean>     <bean  >   <property name="jndiName"><value>testQueue</value></property> </bean>     <bean        >   <property name="targetConnectionFactory"><ref bean="jmsConnFactory"/></property> </bean>     <bean  >   <property name="jndiName"><value>QueueConnectionFactory</value></property> </bean>

The Future

We've focused here on publishing messaging, and seen how Spring can make this much easier. Spring1.3 is scheduled to have sophisticated support for consuming JMS messages, with a powerful alternative to Message Driven Beans for asynchronous processing. This will enable Spring to shoulder more of the responsibility formerly associated with EJBs.



Professional Java Development with the Spring Framework
Professional Java Development with the Spring Framework
ISBN: 0764574833
EAN: 2147483647
Year: 2003
Pages: 188

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net