Asynchronous Messaging Made Easy With Spring JMS
by Srini Penchikala
02/22/2006
Asynchronous process communication is an important part of a
service-oriented architecture (SOA), since many system communications in
an enterprise, especially those with external organizations, are
asynchronous in nature. Java Message Service
(JMS) is the API used to write JEE applications using asynchronous
messaging. A traditional messaging implementation using the JMS API
involves steps like JNDI lookups for the queue connection factory and Queue
resources and creating a JMS session before actually sending or receiving a message.
The Spring framework simplifies the task of working with JEE components, including JMS. It provides a template mechanism to hide the details of a typical JMS implementation so developers can concentrate on the actual task of processing messages instead of worrying about how to create, access, or clean up JMS resources.
This article provides an overview of the Spring JMS API and how to use it to process (send and receive) messages asynchronously in a sample web application running in a JBoss MQ server. I will compare the traditional way of implementing JMS with a Spring JMS implementation to show how easy and flexible it is to process messages using Spring JMS.
Asynchronous Messaging and SOA
In the real world, most web requests are processed synchronously. For example, when a user logs into a website, he or she enters a user name and password and the server authenticates the login credentials. If authentication is successful, the program lets the user into the website. Here, the login request is processed immediately after it's received from the client. Credit card authorization is another example of a synchronous process; a customer is allowed to proceed only after the server verifies that the entered credit card number is valid and the customer has sufficient credit in the account. But let's consider the payment settlement step in an order processing system. Once the system verifies that the user's credit card information is accurate and there are sufficient funds in the account, it doesn't need to wait until all of the payment details are finalized and fund transfer is completed. The payment settlement is done in an asynchronous manner so the customer can proceed with the checkout process.
Related Reading Spring: A Developer's Notebook |
Asynchronous processing is used for the requests that take a longer time to process than a typical synchronous request. Another example of an asynchronous process is the credit request process submitted to an Automated Underwriting System (AUS) in a home loan processing application. After a borrower submits the loan application, the mortgage company sends a request to AUS for credit history information. Since this request is for a comprehensive credit report with details such as borrower's current and past credit accounts, late payments, and other financial details, it usually takes longer time (hours or sometimes even days) to get a response for these requests. It doesn't make sense for the client program (application) to open a connection to the server and wait that long for the results. So the communication occurs asynchronously; i.e., once the request is submitted, it is placed in a queue and the client disconnects from the server. Then the AUS service picks up the request from the specified queue, processes it, and puts the result message in another message queue. Finally, the client program will pick up the result from the queue and continue with processing the credit history results.
JMS
If you have worked with JMS, it's very similar to writing JDBC or JCA code. It involves boilerplate code to create or retrieve JMS resource objects that make the task more code-intensive and repetitive every time you need to write a new class to send or receive a message. The following list shows the steps involved in a traditional JMS implementation:
- Create JNDI initial context.
- Get a queue connection factory from the JNDI context.
- Get a
Queue
from the queue connection factory. - Create a
Session
object. - Create a sender or receiver object.
- Send or receive a message using the sender or receiver object created in Step 5.
- Close all of the JMS resources after processing the messages.
As you can see, Step 6 is the only place where the message processing is done. The other steps are there just to manage JMS resources, which has nothing to do with the actual business requirements, but developers have to write and maintain code for these additional steps.
Spring JMS
The Spring framework provides a template mechanism to hide the details of Java APIs. JEE developers can use JDBCTemplate
and JNDITemplate
classes to access a back-end database and JEE resources (data sources,
connection pools), respectively. JMS is no exception. Spring provides
the JMSTemplate
class so developers don't have to write the
boilerplate code for a JMS implementation. The following are some of
the advantages provided by Spring when developing JMS applications.
- It provides a JMS abstraction API that simplifies the use of JMS to access the destinations (queues or topics) and publish messages to the specified destinations.
- JEE developers don't need to be concerned about the differences between different JMS versions (such as JMS 1.0.2 versus JMS 1.1).
- The developers don't need to specifically deal with JMS exceptions, as Spring provides an unchecked exception for any JMS exception that is rethrown in JMS code.
Once you start using Spring in JMS applications, you will appreciate its simplicity for asynchronous messaging. The Spring JMS framework offers a variety of Java classes to make it easy to work with JMS applications. Table 1 lists some of these classes.
Table 1. Spring JMS classes
Class Name | Package | Function |
---|---|---|
JmsException |
org.springframework.jms |
This is the base (abstract) class for any exceptions thrown by Spring framework whenever there is a JMS exception. |
JmsTemplate , JmsTemplate102 |
org.springframework.jms.core |
These are helper classes used to simplify the use of
JMS by handling the creation and release of JMS resources like
connection factories, destinations, and sender/receiver objects. JmsTemplate102 is a subclass of JmsTemplate that uses the JMS 1.0.2 specification. |
MessageCreator |
org.springframework.jms.core |
This is a callback interface used by the JmsTemplate class. It creates a JMS message for a specified session. |
MessageConverter |
org.springframework.jms.support.converter |
This interface acts as an abstraction to convert between Java objects and JMS messages. |
DestinationResolver |
org.springframework.jms.support.destination |
This is an interface used by JmsTemplate for resolving destination names. DynamicDestinationResolver and JndiDestinationResolver are the default implementations of this interface. |
In the following sections, I will explain some of the classes listed in Table 1 (such as JmsTemplate
, DestinationResolver
, and MessageConverter
) in more detail.
JMSTemplate
JmsTemplate
provides several helper methods to perform basic operations. To get started using JmsTemplate
you will need to know which JMS specification is supported by the JMS provider. JBoss AS 4.0.2 and WebLogic 8.1 servers support the JMS 1.0.2 specification. WebLogic Server 9.0 includes support for the JMS 1.1
specification. JMS 1.1 unifies the programming interfaces for
Point-to-Point (PTP) and Publish/Subscribe (Pub/Sub) domains. As result
of this change, developers can create a transacted session, then receive
a message from a Queue
(PTP) and send another message to a Topic
(Pub/Sub) within the same JMS transaction. JMS 1.1 is
backwards-compatible with JMS 1.0 so the code written for the JMS 1.0
specification should still work with version 1.1.
JmsTemplate
provides various methods to send and receive the messages. Table 2 shows a list of some of these methods.
Table 2. JMS template methods
Method Name | Function |
---|---|
send |
Send a message to the default or specified destination. JmsTemplate contains send methods that specify the destination using a javax.jms.Destination object or using JNDI lookup. |
receive |
Receive a message from the default or specified
destination, but only wait until a specified time for delivery. We
specify the timeout via the receiveTimeout attribute. |
convertAndSend |
This method delegates the conversion process to an instance of MessageConverter interface and then sends the message to the specified destination. |
receiveAndConvert |
Receive a message from the default or specified destination and convert the message to Java object. |
Destinations are stored and retrieved using a JNDI context. When configuring a Spring application context, we use the JndiObjectFactoryBean
class to get references to JMS destinations. The DestinationResolver
interface is used to resolve a destination name to a JMS destination,
which is helpful when the application has lot of destinations. DynamicDestinationResolver
(a default implementation of DestinationResolver
) is used to resolve dynamic destinations.
The MessageConverter
interface defines a contract to
convert Java objects into JMS messages. By using the converter,
application code can focus on the business objects and not bother with
the inner details of how it's represented as a JMS message. SimpleMessageConverter
(and SimpleMessageConverter102
) are default implementations of MessageConverter
. They are used to convert a String
to a JMS TextMessage
, a byte array (byte[]
) to a JMS BytesMessage
, a Map
to a JMS MapMessage
, and a Serializable
object to a JMS ObjectMessage
, respectively. You can also write your own custom implementations of MessageConverter
to convert XML documents into a TextMessage
object using an XML binding framework such as JAXB, Castor, Commons Digester, XMLBeans, or XStream.
Sample Application
I will use a sample loan application processing system (called LoanProc) to demonstrate how to use Spring in a JMS application. As part of the loan processing, LoanProc sends loan details (loan ID, borrower name, borrower's SSN, loan expiration date, and loan amount) to request the credit history details from the AUS system. To keep it simple, we will get credit history details based on two loan parameters: credit score (also known as FICO score) and loan amount. Let's assume that the business rules for processing a credit check request are as follows:
- If the loan amount is equal to or less than $500,000, then the borrower must have at least a "Good" credit (i.e., the borrower's FICO score is between 680-699).
- If the loan amount is greater than $500,000, the borrower must have a "Very Good" credit, meaning his/her credit score is more than 700.
Loan Application Use Case
The use case for the credit request process consists of the following steps:
- The user enters loan details on the loan application web page and submits the loan application.
- Then the program sends loan details to the AUS system to get credit
history details. This is done by sending the request to a message queue
called
CreditRequestSendQueue
. - The AUS system picks up loan details from the queue and uses loan parameters to retrieve credit history information from its database.
- Then AUS creates a new message with its findings on the borrower's credit history and sends it to a new message queue called
CreditRequestReceiveQueue
. - Finally, LoanProc picks up the response message from receive queue and processes the loan application to determine if the application can be approved or denied.
In the sample application, both message queues are configured in the same JBoss MQ server. The use case is represented in the Sequence Diagram in Figure 1.
Figure 1. Sequence diagram of the loan processing application (Click the screen shot to open a full-size view)
Technologies
Table 3 below shows the different technologies and open source frameworks I used in the sample application listed by the application tier.
Table 3. Frameworks used in the JMS application
Tier | Technology/Framework |
---|---|
MVC | Spring MVC |
Service | Spring Framework (version 2.1) |
JMS API | Spring JMS |
JMS Provider | JBoss MQ (version 4.0.2) |
JMS Console | Hermes |
IDE | Eclipse 3.1 |
JMS Resource Setup Using Hermes
To process a message asynchronously, first we need the message queues to send and receive messages. We can create a new message queue using the configuration XML file in JBoss and then browse the queue details using a JMS console. Listing 1 shows the XML configuration snippet for the JMS configuration. (This should be added to the jbossmq-destinations-service.xml file located in the %JBOSS_HOME%\server\all\deploy-hasingleton\jms directory.)
Listing 1. Configuration of the JMS queue in JBoss MQ Server
<!-- Credit Request Send Queue --> <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=CreditRequestSendQueue"> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> </mbean> <!-- Credit Request Receive Queue --> <mbean code="org.jboss.mq.server.jmx.Queue" name="jboss.mq.destination:service=Queue,name=CreditRequestReceiveQueue"> <depends optional-attribute-name="DestinationManager"> jboss.mq:service=DestinationManager </depends> </mbean>
Now, let's look at how to browse a message queue using a JMS tool called Hermes. Hermes is a Java Swing application that can be used to create, manage, and monitor JMS destinations in JMS providers such as JBossMQ, WebSphereMQ, ActiveMQ and Arjuna servers. Download Hermes from its website and extract the .zip file to a local directory (for example, c:\dev\tools\hermes). Once it's installed, double-click the file hermes.bat (located in the bin directory) to launch the program.
To configure JBossMQ server in Hermes, refer to this demo on the Hermes website. It has excellent step-by-step visual instructions for JBoss MQ configuration. Enter the following information when configuring a new JNDI initial context.
- providerURL = jnp://localhost:1099
- initialContextFactory = org.jnp.interfaces.NamingContextFactory
- urlPkgPrefixes = org.jnp.interfaces:org.jboss.naming
- securityCredentials = admin
- securityPrincipal = admin
Enter queue/CreditRequestSendQueue
and queue/CreditRequestReceiveQueue
when you create new destinations. Figure 2 shows the main screen of the JMS console with the new message queues created for the sample JMS application.
Figure 2. Screenshot of all destinations in Hermes. (Click on the screen shot to open a full-size view)
Figure 3 below shows a screenshot of the Hermes JMS console with message queue details after sending a few messages to CreditRequestSendQueue
from the message sender class. You can see there are five messages in
the queue and the console shows the message details such as message ID,
message destination, time stamp, and the actual message.
Figure 3. Screenshot of queue details in Hermes. (Click on the screen shot to open a full-size view)
These queue names and other JMS and JNDI parameters used in the sample application are shown in Table 4.
Table 4. Spring JMS Configuration Parameters
Parameter Name | Parameter Value |
---|---|
Initial Context Factory | org.jnp.interfaces.NamingContextFactory |
Provider URL | localhost:8080 |
Initial Context Factory URL Packages | org.jnp.interfaces:org.jboss.naming |
Queue Connection Factory | UIL2ConnectionFactory |
Queue Name | queue/CreditRequestSendQueue, queue/CreditRequestReceiveQueue |
Spring Configuration
Now that we have the JMS destinations required to run the sample application, it's time to get into the details of wiring the JMS components using an XML Spring configuration file (called spring-jms.xml). The components are wired with JMS object instances using the setter injection principle in the Inversion of Controller (IOC) design pattern. Let's look at the components in detail, showing a XML configuration snippet for each JMS component.
JNDI context is the starting point in getting the JMS resources, so
first we configure a JNDI template. Listing 2 shows a Spring bean named jndiTemplate
with the usual parameters required to get the JNDI initial context.
Listing 2. JNDI context template
<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate"> <property name="environment"> <props> <prop key="java.naming.factory.initial"> org.jnp.interfaces.NamingContextFactory </prop> <prop key="java.naming.provider.url"> localhost </prop> <prop key="java.naming.factory.url.pkgs"> org.jnp.interfaces:org.jboss.naming </prop> </props> </property> </bean>
Next, we configure the queue connection factory. Listing 3 shows the configuration for the queue connection factory.
Listing 3. JMS queue connection factory configuration
<bean id="jmsQueueConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>UIL2ConnectionFactory</value> </property> </bean>
We define two JMS destinations to send and receive the messages. Listings 4 and 5 show these details.
Listing 4. Configuration for send queue
<bean id="sendDestination" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>queue/CreditRequestSendQueue</value> </property> </bean>
Listing 5. Configuration for receive queue
<bean id="receiveDestination" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiTemplate"> <ref bean="jndiTemplate"/> </property> <property name="jndiName"> <value>queue/CreditReqeustReceiveQueue</value> </property> </bean>
Then we configure the JmsTemplate
component. We use JmsTemplate102
for the sample application. We use the defaultDestination
attribute to specify JMS destination.
Listing 6. JMS template configuration
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate102"> <property name="connectionFactory"> <ref bean="jmsQueueConnectionFactory"/> </property> <property name="defaultDestination"> <ref bean="destination"/> </property> <property name="receiveTimeout"> <value>30000</value> </property> </bean>
Finally we configure sender and receiver components. Listings 7 and 8 show the configuration of Sender
and Receiver
objects.
Listing 7. JMS Sender
configuration
<bean id="jmsSender" class="springexample.client.JMSSender"> <property name="jmsTemplate"> <ref bean="jmsTemplate"/> </property> </bean>
Listing 8. JMS Receiver
configuration
<bean id="jmsReceiver" class="springexample.client.JMSReceiver"> <property name="jmsTemplate"> <ref bean="jmsTemplate"/> </property> </bean>
Testing & Monitoring
I wrote a test class called LoanApplicationControllerTest
to test the LoanProc application. We use this class to set the loan parameters and call the credit request service class.
Let's look at the message sender implementation using the traditional JMS development approach without the Spring JMS API. Listing 9 shows the sendMessage
method in MessageSenderJMS
class with all the steps required to process a message using the JMS API.
Listing 9. Traditional JMS implementation
public void sendMessage() { queueName = "queue/CreditRequestSendQueue"; System.out.println("Queue name is " + queueName); /* * Create JNDI Initial Context */ try { Hashtable env = new Hashtable(); env.put("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory"); env.put("java.naming.provider.url","localhost"); env.put("java.naming.factory.url.pkgs", "org.jnp.interfaces:org.jboss.naming"); jndiContext = new InitialContext(env); } catch (NamingException e) { System.out.println("Could not create JNDI API " + "context: " + e.toString()); } /* * Get queue connection factory and queue objects from JNDI context. */ try { queueConnectionFactory = (QueueConnectionFactory) jndiContext.lookup("UIL2ConnectionFactory"); queue = (Queue) jndiContext.lookup(queueName); } catch (NamingException e) { System.out.println("JNDI API lookup failed: " + e.toString()); } /* * Create connection, session, sender objects. * Send the message. * Cleanup JMS connection. */ try { queueConnection = queueConnectionFactory.createQueueConnection(); queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); queueSender = queueSession.createSender(queue); message = queueSession.createTextMessage(); message.setText("This is a sample JMS message."); System.out.println("Sending message: " + message.getText()); queueSender.send(message); } catch (JMSException e) { System.out.println("Exception occurred: " + e.toString()); } finally { if (queueConnection != null) { try { queueConnection.close(); } catch (JMSException e) {} } } }
Now, let's look at the message sender implementation using Spring. Listing 10 shows the code for the send
method in the MessageSenderSpringJMS
class.
Listing 10. JMS implementation using Spring API
public void send() { try { ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] { "spring-jms.xml"}); System.out.println("Classpath loaded"); JMSSender jmsSender = (JMSSender)appContext.getBean("jmsSender"); jmsSender.sendMesage(); System.out.println("Message sent using Spring JMS."); } catch(Exception e) { e.printStackTrace(); } }
As you can see, all the steps related to managing JMS resources are handled by the Spring container using the configuration file. We just need to get a reference to a JMSSender
object and then call sendMessage
on this object.
Conclusions
In this article, we looked at how the Spring framework simplifies working with asynchronous messaging applications using the JMS API. Spring takes away all of the boilerplate code required to process a message using JMS, such as getting a queue connection factory and creating queue and session objects from Java code and wiring them at runtime using a configuration file. We can swap the JMS resource objects dynamically without having to modify any Java code, thanks to the power of the Inversion of Control (IOC) principle.
Since async messaging is an integral part of a SOA framework, Spring fits nicely into the SOA toolset. Also, a JMS management tool such as Hermes makes it easy to create, manage, and administer the JMS resources, especially for system administrators.
Resources
- Sample code for this article
- Spring JMS documentation
- "1-2-3 Messaging with Spring JMS"
- JBoss MQ wiki
Srini Penchikala is an information systems subject matter expert at Flagstar Bank.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.