Java Message Service

When it comes to distributed computing, JMS is Eric's tool of choice when reliability and performance are top priorities.


July 02, 2007
URL:http://www.drdobbs.com/security/java-message-service/jvm/java-message-service/200001958

With distributed computing, many software components of one working system may be separated geographically. The need for these components to communicate is obvious, and this need drives many software design decisions. For instance, you might choose to use CORBA, SOAP, or message-oriented middleware for intercomponent communication.

SOAP-based web-service development continues to grow, and uses XML and HTTP to remove the implementation details from remote procedure calls. But while SOAP has broken new ground in distributed computing, message-oriented middleware such as the Java Message Service (JMS) is still my tool of choice when reliability, performance, and security are top priorities.

JMS is a specification (java.sun.com/products/jms) that describes the properties and behavior of an information pipe for Java software. It also describes how Java client applications interact with the information pipe. In this article, I examine messaging concepts and implement a JMS application.

What Is Messaging?

The concept of messaging begins with the goal of delivering data. Enterprise messaging forms the basis for an infrastructure dedicated to communication between disparate components in distributed software systems. The important components in messaging systems—producers, consumers, and the messages themselves—are abstracted via interfaces. The result is a set of loosely coupled components that are part of a cohesive software system. Components that are loosely coupled have as few direct interactions with one another as possible. This isolation leads to more robust software, as changes to one part of the system do not ripple through to other parts. A good messaging system does this by abstracting the components from one another in the system. (For more information, see my book Java Messaging, Delmar-Thomson Learning Inc., 2006.)

Every messaging system consists of a broker component that is responsible for delivering messages to and from the various components interacting with the messaging system. Messaging brokers generally treat messages as opaque, meaning they tend to ignore the content within the message. In fact, the broker does not need to know the purpose or content of a message to deliver it. In turn, the software components that send/receive the messages don't need to know how the messages are delivered, or which components sent them. The important part is that they are sent and received reliably.

JMS Message Paradigms

JMS supports two main message paradigms:

JMS can support the messaging concept of request-and-reply through both of these messaging domains.

Request-and-reply messaging is a common form of client-server communication, where a client makes a request to a server, and the server sends back a response. One of the most familiar implementations of this paradigm is the communication between a web browser and web server; see Figure 1. In this exchange, the client sends a request to the server, and the server responds with the requested data.

Figure 1: Request-and-reply messaging.

Queue-Based Messaging

The JMS point-to-point domain, otherwise known as queue-based messaging or store-and-forward, is normally used when offline message processing is required. The classic example is an e-mail system. If you're currently logged on and an e-mail arrives, you see it immediately and can read it. If you shut down your system, your e-mail is safely and reliably stored for you to view at a later time (see Figure 2).

Figure 2: E-mail inbox implemented as a store-and-forward queue.

When the e-mail reader application is not running, sent e-mail messages are safely stored. Once the e-mail reader application is started, the e-mail messages are delivered.

An e-mail inbox is basically a queue. When users start the e-mail reader application, the messages that were safely stored in the queue are delivered. The e-mail application's display should indicate that new e-mail messages are present, and users can view them (see Figure 3). Once delivered, the message is removed from the "Inbox" queue. It may be placed on another queue that holds messages that have been read, or it may be discarded altogether. That decision is application specific, although the mechanics of the queue are the same for all applications.

Figure 3: When the e-mail reader starts, messages are delivered from the queue to the reader application.

In JMS, a queue is represented by the javax.jms.Queue interface, which extends the Destination interface, and it's either defined by an administrator or the client application at runtime. One or more message producers can place messages onto the same queue, and one or more message consumers can listen to a queue to receive messages. However, each message is delivered to only one consumer. Having multiple consumers listen to the same queue helps balance the load of message processing, or ensures that if one consumer fails, there is another there to continue processing messages off of the queue. Regardless, the behavior is the same—each message on the queue is processed exactly once.

A queue has the added benefit that it stores messages even when there are no listeners available. This feature combined with the queue's exactly-once processing behavior makes the JMS queue the usual choice for reliable, point-to-point messaging in distributed software systems.

Sample E-mail Sender Application

The queue application I present here includes two JMS client applications—one that sends simulated e-mail messages to an e-mail inbox, and another that reads the e-mail messages off the queue. The EmailSender class in Listing One is a message producer that sends messages to a simulated e-mail inbox, which is simply a JMS queue that is read by the e-mail reader application at a later time. (The complete source code for the sender/reader application is available electronically; see www.ddj.com/code/.)

package emaildemo;
import javax.jms.*;
import javax.naming.*;

public class EmailSender 
{
    private Connection connection = null;
    private Session session = null;
    private MessageProducer prod = null;

    public EmailSender()
    {
        try {
            InitialContext jndi = new InitialContext();
           // Lookup a JMS connection factory
           ConnectionFactory conFactory =
                (ConnectionFactory)jndi.lookup("ConnectionFactory");
            // Create a JMS connection
            connection = conFactory.createConnection("","");
            // Create a JMS session object
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            // Get the JMS queue that represents the simulated inbox
           Destination inbox = (Destination)jndi.lookup("Inbox");
           if ( inbox == null )
                inbox = session.createQueue("Inbox");
            // Create a producer to place messages into the inbox
            prod = session.createProducer(inbox);
            // The Connection must be started for messages to fly
            connection.start();
        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }
    public void sendEmail(String subject, String body) throws Exception
    { ... }
    public static void main(String[] args) 
    { ... }
}

Listing One

The EmailSender class initializes the JMS session in its constructor, and contains one additional method that lets the caller send e-mail messages to the queue representing the simulated e-mail inbox.

In the constructor, the first step is to use Java Naming and Directory Interface (java.sun.com/products/jndi) to get access to the JMS provider's javax.jms.ConnectionFactory object. The provider-specific JNDI details are typically placed within a configuration file named jndi.properties. The Java VM automatically looks for the presence of this file when you instantiate the InitialContext class.

Once a ConnectionFactory object is obtained, a call to its createConnection method returns a JMS javax.jms.Connection object. This object represents an actual connection to the JMS provider. Because it's threadsafe and consumes a considerable amount of system resources to maintain, your application should require only one active Connection object during its lifetime. Opening multiple connections to a JMS provider is usually unnecessary and a waste of resources.

Next, via a call to the Connection object's createSession method, the code creates a javax.jms.Session object. A Session object represents a working message session, and can only be used by one thread at a time within your code. Therefore, you should create a Session object for each message producer and consumer thread in your application. The Session object is used as a factory to create javax.jms.Destination, javax.jms.Producer, javax.jms.Consumer, and javax.jms.Message objects.

The Session object is used to create a reference to the "Inbox" queue by looking it up via JNDI and creates it on the fly if it's not found. Next, a message producer is created for the "Inbox" queue, which is used to send simulated e-mail messages. Finally, a call is made to the Connection object's start method to begin the flow of messages for the queue (and all destinations created through this JMS connection).

The sample application instantiates the EmailSender class (available electronically) and calls its sendEmail method twice to simulate sending two individual e-mail messages to the "Inbox" queue. The sendEmail method first creates a plain-old Java object (POJO) that represents the e-mail message, of class Email. Next, the Session object (which was created in the constructor) is used to create a javax.jms.ObjectMessage object that in turn is used to encapsulate the Email POJO. Finally, the JMS message is actually sent via a call to the send method on the queue's Producer object.

Running the e-mail sender application results in the output in Figure 4. Because the simulated inbox is implemented as a JMS queue, the reader application does not need to be running at the same time as the sender.

Figure 4: Running e-mail sender application.

All of the messages sent to the queue will remain in the queue until the reader application executes and removes them.

Sample E-mail Reader Application

The reader application performs many of the same steps as the sender in terms of initializing its JMS connection and session. The differences are:

Once initialized, the application is free to continue with its own processing. In the case of an actual e-mail client application, this may involve letting users compose new e-mail messages, organize existing e-mail into folders, and so on. The provider eventually calls back on the application's MessageListener object when a new message arrives. This is the way JMS implements asynchronous messaging, which lets the client application perform other tasks while waiting for messages to arrive.

JMS also supports synchronous messaging, which requires the client application (or one of its child threads) to block until a message arrives. This type of an application is strictly an event-driven application, meaning it cannot do anything unless certain events occur; a JMS message arrival, in this case. This type of application can be developed to respond differently to different messages received, and can in effect be driven remotely by sending it the proper messages in the proper order.

Again, the setMessageListener method was called with a reference to an object that implements the MessageListener interface. Because of this, once the JMS connection is started, the provider asynchronously calls the client application's onMessage method when a message arrives for the destination the consumer is listening to; see the EmailReader class's implementation of onMessage (available electronically). The javax.jms.Message object is provided as a parameter to the onMessage method, representing the received JMS message. Once the message is cast to a javax.jms.ObjectMessage object, the Email POJO is then extracted via a call to getObject. For illustrative purposes, the remainder of the code simply prints out the e-mail's subject and body (see Figure 5).

When this application is executed, it receives all of the messages, one by one, that were placed on the queue earlier by the e-mail sender application. Once all of the messages have been received, the application simply waits to be notified that another message has been placed on the queue.

Conclusion

Writing JMS client applications is straightforward once you understand the basics. The power of JMS is in the ability to leverage built-in transaction management, and reliable message delivery, without knowing much more than the basics.


Eric is DDJ contributing editor and a consultant in New York. He has worked extensively in Java and C++ developing real-time trading and financial applications. He can be contacted at [email protected].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.