Idiot-maker

  :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

http://onjava.com/lpt/a/6490

 

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
By Justin Gehtland

 

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:

  1. Create JNDI initial context.
  2. Get a queue connection factory from the JNDI context.
  3. Get a Queue from the queue connection factory.
  4. Create a Session object.
  5. Create a sender or receiver object.
  6. Send or receive a message using the sender or receiver object created in Step 5.
  7. 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.

  1. 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.
  2. 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).
  3. 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 NamePackageFunction
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 NameFunction
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:

  1. 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).
  2. 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:

  1. The user enters loan details on the loan application web page and submits the loan application.
  2. 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.
  3. The AUS system picks up loan details from the queue and uses loan parameters to retrieve credit history information from its database.
  4. 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.
  5. 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.

Thumbnail; click for full-size image.
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

TierTechnology/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.

Thumbnail, click for full-size image.
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.

Thumbnail, click for full-size image.
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 NameParameter 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

Srini Penchikala is an information systems subject matter expert at Flagstar Bank.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.

posted on 2012-11-28 13:42  NickyYe  阅读(2327)  评论(0编辑  收藏  举报