In this post, we will introduce the core concepts of messaging, as well as the rich support for various types of messaging that the Spring framework and its sister projects provide.
What is Messaging? To best explain this, I'll paraphrase the example offered by the groundbreaking Enterprise Integration Patterns book by Gregor Hohpe and Bobby Woolf (Addison Wesley, 2004). When you make a telephone call, you attempt to relay information to another party. This only works if the second party is available when you place the phone call. Because it is not always possible to answer phone calls, we use a voice-mail boxes to queue the messages. Callers leave messages in the voice-mail box and the callee is then free to retrieve the message (or, indeed, many messages) at a later point, asynchronously.
In that example, a voice-mail box sits in the middle of the two parties. It stores the message and then delivers it when the callee – the recipient – retrieves it. In the world of enterprise messaging, things work very much the same: a party sends a message to a messaging broker (also known as messaging-oriented middle-ware – MOM) and another party – when that party can – takes delivery of, or explicitly queries for, any messages from the message broker.
Here is where the analogy stops being useful. Message brokers, in contrast to voice-mail boxes, have a lot of options. Message brokers are ideally positioned to provide extra services, like routing, and to make guarantees about message delivery. Message brokers can be optimized for different use cases, for example, you can trade speed for durability. Message brokers may persist messages to an external store to ensure durability, though this is typically a configuration that can be toggled in the name of speed.
In the voice-mail box example, a message was sent by one party and then delivered to another – the communication was point-to-point. Message brokers support this, as well as another type of communication called publish-subscribe, where messages are delivered to multiple clients.
A common use of message brokers is to solve integration problems between two different systems. Data sent to a message broker is usually of a format common to both the sender and recipient of the message. The only thing that two systems need to agree on to use a message broker is the data contract. Messages typically have a message body, in which the contents of the message itself are stored, and message headers, which are key / value pairs that provide meta-data about the body of the message that can be used to aid consumers of the messages in processing the message. Message headers can be anything you like, but they typically relate to the message itself, or to the processor of the message.
Java Message Service
The Java Message Service (JMS) API specifies client interfaces for interacting with message brokers. Each message broker provides its own implementation of the API, very much like JDBC drivers do for the JDBC API. This implies that JMS clients should generally use the same version of the client as the server. There are many, many fine JMS broker implementations to choose from. One reason for this is that messaging ihas always been an important part of application development, and continues to be even more so today. JMS has been a part of the J2EE (now Java EE) specifications since 1.1. The JMS specification has been at version 1.1 for most of the last decade.
In JMS, clients use a javax.jms.ConnectionFactory
to create a javax.jms.Connection
. The Connection
can then be used to create a javax.jms.Session
. The Session
represents the client interaction with the broker and allows for sending and receiving messages as well as other less obvious operations.
The most useful methods on the interface concern the creation of a message producers, and message consumers that send and receive messages to and from a javax.jms.Destination
. A Destination
maps the JMS concept of an "address" on a message broker. It also maps the concept of where a broker stores messages. In JMS, messages are sent to, stored in, and consumed from the same place, all represented by a javax.jms.Destination
instance.
[caption id="attachment_7506" align="alignnone" width="573" caption="Above, blue elements represent producers and consumers. The orange elements represent destinations in the broker where messages are buffered. In JMS, these are either topics or queues."]
[/caption]
Destination
is an interface and has two more specific sub-interfaces, javax.jms.Queue
and javax.jms.Topic.
A Queue
represents a standard queue, which is a point-to-point construct as described before. A Topic
provides publish-subscribe messaging and can deliver a single message to multiple recipients.
To send a message to a Destination
, you must create a javax.jms.MessageProducer
. The MessageProducer
can then be used to send javax.jms.Message
s.
JMS supports two different mechanisms to receive messages. The first way is to ask for a message, using the javax.jmx.MessageConsumer#receive()
method, which returns an individual message from a Destination
in a synchronous manner; the method blocks until a message is received, by default. Instead of using a MessageConsumer
, clients may install a javax.jms.MessageListener
by calling javax.jms.Session#setMessageListener(MessageListener)
. MessageListener
is an interface and has only one method, public void onMessage(javax.jms.Message)
, which will be called whenever a javax.jms.Message
is available for consumption on a Destination
. A MessageListener
provides asynchronous message processing: as messages arrive, they are processed.
There is quite a bit more to learn in the JMS API, but these classes and concepts will help you most in our discussion of Spring's support for JMS messaging. The first level of support is the org.springframework.jms.core.JmsTemplate
, which provides simplifying methods to reduce the things we just discussed to one-liners. The JmsTemplate
, requires a javax.jms.ConnectionFactory
instance to do its work. JmsTemplate
can do a lot of work for you. For example, to send a message, the JmsTemplate
establishes a javax.jms.Session
, sets up a javax.jms.MessageConsumer
or javax.jms.MessageProducer
, sets up all the machinery for transactions, and provide you with a reference to the current javax.jms.Session
so you can create the message of your choice and send it. With all the error handling and construction logic, that's easily a savings of dozens of lines of code. Once your message has been sent, it destroys or closes most of those objects. This is standard practice in application servers (like a Java EE server) because the ConnectionFactory
instances are created by the server, managed by the server, and are pooled. They cache the instances after use. Closing resources in those environments simply returns them to a pool. So, the JmsTemplate
does the right thing in the standard case, assuming the ConnectionFactory
caches or pools instances.
In a managed environment like an application server, you will typically need to acquire the javax.jms.ConnectionFactory
from JNDI. You can use Spring to lookup that reference for you and configure a JmsTemplate
. In our examples, we want to operate more loosely, so we will use the standalone ActiveMQ message broker. ActiveMQ is a popular, open-source message broker. To use it, download it, and then run the startup script appropriate to your operating system in the bin folder. In your application, you'll need the client libraries to connect for the corresponding version of ActiveMQ. At the time of this writing, the latest version of ActiveMQ was 5.4.2. If you are using Maven, add the following dependencies to your Maven pom file:
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-optional</artifactId>
<version>${activemq.version}</version>
</dependency>
Be sure to either create a Maven property for ${activemq.version}
or replace the string manually with the appropriate version. There is an activemq-all
dependency out there as well, but it brings down a lot of perhaps unnecessary jars. For our application, the two dependencies above suffice.
Using Spring with JMS
Let's examine the configuration for a basic JMS application. First, let's examine the basic Spring XML configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/200…