Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (15.81 MB, 597 trang )
Chapter 13 ■ Messaging
JMS 1.0 made a clear difference between the point-to-point and publish-subscribe model. It defined two
domain-specific APIs, one for point-to-point (queues) and one for pub-sub (topics). That’s why you can find
QueueConnectionFactory and TopicConnectionFactory API instead of the generic ConnectionFactory for example.
Note also the different vocabulary; a consumer is called a receiver in P2P and a subscriber in pub-sub.
The JMS 1.1 API (referred to as the classic API) provided a unified set of interfaces that can be used with both P2P
and pub-sub messaging. Table 13-1 shows the generic name of an interface (e.g., Session) and the legacy names for
each model (QueueSession, TopicSession).
Table 13-1. Interfaces Depending on JMS Version
Classic API
Simplified API
Legacy API (P2P)
Legacy API (Pub-Sub)
ConnectionFactory
ConnectionFactory
QueueConnectionFactory
TopicConnectionFactory
Connection
JMSContext
QueueConnection
TopicConnection
Session
JMSContext
QueueSession
TopicSession
Destination
Destination
Queue
Topic
Message
Message
Message
Message
MessageConsumer
JMSConsumer
QueueReceiver
TopicSubscriber
MessageProducer
JMSProducer
QueueSender
TopicPublisher
JMSException
JMSRuntimeException
JMSException
JMSException
But JMS 1.1 was still a verbose and low-level API compared to JPA or EJB. JMS 2.0 introduces a simplified API that
offers all the features of the classic API but requires fewer interfaces and is simpler to use. Table 13-1 highlights the
differences between these APIs (all located under the javax.jms package).
I will not discuss the legacy API but I need to introduce the classic API; first of all because you will still find
millions of lines of code using the JMS 1.1 classic API and second, because technically the simplified API relies on the
classical one.
Classic API
The JMS classic API provides classes and interfaces for applications that require a messaging system (see Figure 13-7).
This API enables asynchronous communication between clients by providing a connection to the provider, and a session
where messages can be created and sent or received. These messages can contain text or other different kinds of objects.
423
www.it-ebooks.info
Chapter 13 ■ Messaging
Figure 13-7. JMS Classic API
ConnectionFactory
Connection factories are administered objects that allow an application to connect to a provider by creating a
Connection object programmatically. A javax.jms.ConnectionFactory is an interface that encapsulates the
configuration parameters that have been defined by an administrator.
To use an administered object such as a ConnectionFactory, the client needs to perform a JNDI lookup (or use
injection). For example, the following code fragment obtains the JNDI InitialContext object and uses it to look up a
ConnectionFactory by its JNDI name:
Context ctx = new InitialContext();
ConnectionFactory ConnectionFactory =
(ConnectionFactory) ctx.lookup("jms/javaee7/ConnectionFactory");
The methods available in this interface (see Listing 13-1) are createConnection methods that return a
Connection object and new JMS 2.0 createContext methods that return a JMSContext. You can create a Connection
or a JMSContext either with the default user identity or by specifying a username and password.
424
www.it-ebooks.info
Chapter 13 ■ Messaging
Listing 13-1. ConnectionFactory Interface
public interface ConnectionFactory {
Connection createConnection() throws JMSException;
Connection createConnection(String userName, String password) throws JMSException;
JMSContext createContext();
JMSContext createContext(String userName, String password);
JMSContext createContext(String userName, String password, int sessionMode);
JMSContext createContext(int sessionMode);
}
Destination
A destination is an administered object that contains provider-specific configuration information such as the destination
address. But this configuration is hidden from the JMS client by using the standard javax.jms.Destination interface.
Like the connection factory, a JNDI lookup is needed to return such objects:
Context ctx = new InitialContext();
Destination queue = (Destination) ctx.lookup("jms/javaee7/Queue");
Connection
The javax.jms.Connection object, which you create using the createConnection() method of the connection
factory, encapsulates a connection to the JMS provider. Connections are thread-safe and designed to be shareable, as
opening a new connection is resource intensive. However, a session (javax.jms.Session) provides a single-threaded
context for sending and receiving messages, using a connection to create one or more sessions. Once you have a
connection factory, you can use it to create a connection as follows:
Connection connection = connectionFactory.createConnection();
Before a receiver can consume messages, it must call the start() method. If you need to stop receiving messages
temporarily without closing the connection, you can call the stop() method:
connection.start();
connection.stop();
When the application completes, you need to close any connections created. Closing a connection also closes its
sessions and its producers or consumers:
connection.close();
Session
You create a session from the connection using the createSession() method. A session provides a transactional
context in which a set of messages to be sent or received are grouped in an atomic unit of work, meaning that, if you
send several messages during the same session, JMS will ensure that either they all are sent or none. This behavior is
set at the creation of the session:
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
425
www.it-ebooks.info
Chapter 13 ■ Messaging
The first parameter of the method specifies whether or not the session is transactional. In the code, the parameter
is set to true, meaning that the request for sending messages won’t be realized until either the session’s commit()
method is called or the session is closed. If the parameter was set to false, the session would not be transactional,
and messages would be sent as soon as the send() method is invoked. The second parameter means that the session
automatically acknowledges messages when they have been received successfully. A session is single-threaded and is
used to create messages, producers, and consumers.
Messages
To communicate, clients exchange messages; one producer will send a message to a destination, and a consumer will
receive it. Messages are objects that encapsulate information and are divided in three parts (see Figure 13-8):
•
A header: contains standard information for identifying and routing the message.
•
Properties: are name-value pairs that the application can set or read. Properties also allow
destinations to filter messages based on property values.
•
A body: contains the actual message and can take several formats (text, bytes, object, etc.).
Figure 13-8. Structure of a JMS message
Header
The header has predefined name-value pairs, common to all messages that both clients and providers use to identify
and route messages. They can be seen as message metadata as they give information about the message. Each
field has associated getter and setter methods defined in the javax.jms.Message interface. Some header fields are
intended to be set by a client, but many are set automatically by the send() or the publish() method. Table 13-2
describes each JMS message header field.
426
www.it-ebooks.info
Chapter 13 ■ Messaging
Table 13-2. Fields Contained in the Header
Field
Description
Set By
JMSDestination
This indicates the destination to which the message is being sent.
send() or publish()
method
JMSDeliveryMode
send() or publish()
JMS supports two modes of message delivery. PERSISTENT mode
instructs the provider to ensure the message is not lost in transit due method
to a failure. NON_PERSISTENT mode is the lowest-overhead delivery
mode because it does not require the message to be
logged to a persistent storage.
JMSMessageID
This provides a value that uniquely identifies each message
sent by a provider.
send() or publish()
method
JMSTimestamp
This contains the time a message was handed off to a provider
to be sent.
send() or publish()
method
JMSCorrelationID
A client can use this field to link one message with another
such as linking a response message with its request message.
Client
JMSReplyTo
This contains the destination where a reply to the message
should be sent.
Client
JMSRedelivered
This Boolean value is set by the provider to indicate
whether a message has been redelivered.
Provider
JMSType
This serves as a message type identifier.
Client
JMSExpiration
When a message is sent, its expiration time is calculated and set
based on the time-to-live value specified on the send() method.
send() or publish()
method
JMSPriority
JMS defines a 10-level priority value, with 0 as the lowest priority
and 9 as the highest.
send() or publish()
method
Properties
In addition to the header fields, the javax.jms.Message interface supports property values, which are just like headers,
but explicitly created by the application, instead of being standard across messages. This provides a mechanism for
adding optional header fields to a message that a client will choose to receive or not via selectors. Property values can
be boolean, byte, short, int, long, float, double, and String. The code to set and get properties looks like this:
message.setFloatProperty("orderAmount", 1245.5f);
message.getFloatProperty("orderAmount");
Body
The body of a message is optional, and contains the data to send or receive. Depending on the interface that you use,
it can contain different formats of data, as listed in Table 13-3.
427
www.it-ebooks.info
Chapter 13 ■ Messaging
Table 13-3. Types of Messages
Interface
Description
StreamMessage
A message whose body contains a stream of Java primitive values. It is filled and read
sequentially.
MapMessage
A message whose body contains a set of name-value pairs where names are strings
and values are Java primitive types.
TextMessage
A message whose body contains a string (for example, it can contain XML).
ObjectMessage
A message that contains a serializable object or a collection of serializable objects.
BytesMessage
A message that contains a stream of bytes.
It is possible to create your own message format, if you extend the javax.jms.Message interface. Note that, when
a message is received, its body is read-only. Depending on the message type, you have different methods to access its
content. A text message will have a getText() and setText() method, an object message will have a getObject() and
setObject(), and so forth:
textMessage.setText("This is a text message");
textMessage.getText();
bytesMessage.readByte();
objectMessage.getObject();
Note that since JMS 2.0, the new method
the specified type.
Sending and Receiving a Message with Classic API
Now let’s take a look at a simple example to get an idea of how to use the classic JMS API to send and receive a
message. JMS employs producers, consumers, and destinations. The producer sends a message to the destination,
where the consumer is waiting for the message to arrive. Destinations can be of two kinds: queues (for point-to-point
communication) and topics (for publish-subscribe communication). In Listing 13-2, a producer sends a text message
to a queue to which the consumer is listening.
Listing 13-2. The Producer Class Produces a Message into a Queue using the Classic API
public class Producer {
public static void main(String[] args) {
try {
// Gets the JNDI context
Context jndiContext = new InitialContext();
// Looks up the administered objects
ConnectionFactory connectionFactory = (ConnectionFactory)
jndiContext.lookup("jms/javaee7/ConnectionFactory");
Destination queue = (Destination) jndiContext.lookup("jms/javaee7/Queue");
// Creates the needed artifacts to connect to the queue
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
428
www.it-ebooks.info
Chapter 13 ■ Messaging
// Sends a text message to the queue
TextMessage message = session.createTextMessage("Text message sent at " + new Date());
producer.send(message);
connection.close();
} catch (NamingException | JMSException e) {
e.printStackTrace();
}
}
}
The code in Listing 13-2 represents a Producer class that has a main() method only. In this method, the first
thing that occurs is that a JNDI context is instantiated and used to obtain a ConnectionFactory and a Destination.
Connection factories and destinations (queues and topics) are called administered objects; they have to be created
and declared in the message provider (in our case, OpenMQ in GlassFish). They both have a JNDI name (e.g., the
queue is called jms/javaee7/Queue) and need to be looked up in the JNDI tree.
When the two administered objects are obtained, the Producer class uses the ConnectionFactory to create a
Connection from which a Session is obtained. With this session, a MessageProducer and a message are created on the
destination queue (session.createProducer(queue)). The producer then sends this message (of type text). Note that
this main class catches the JNDI NamingException as well as the checked JMSException.
Fortunately, once you’ve written this code to send a message, the code to receive it looks almost the same. In fact,
the first lines of the Consumer class in Listing 13-3 are exactly the same: create a JNDI context, lookup for the connection
factory and the destination, and then connect. The only differences are that a MessageConsumer is used instead of a
MessageProducer, and that the receiver enters an infinite loop to listen to the queue (you’ll later see that this loop can be
avoided by using the more standard message listener). When the message arrives, it is consumed and the content displayed.
Listing 13-3. The Consumer Class Consumes a Message from a Queue using the Classic API
public class Consumer {
public static void main(String[] args) {
try {
// Gets the JNDI context
Context jndiContext = new InitialContext();
// Looks up the administered objects
ConnectionFactory connectionFactory = (ConnectionFactory)
jndiContext.lookup("jms/javaee7/ConnectionFactory");
Destination queue = (Destination) jndiContext.lookup("jms/javaee7/Queue");
// Creates the needed artifacts to connect to the queue
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(queue);
connection.start();
// Loops to receive the messages
while (true) {
TextMessage message = (TextMessage) consumer.receive();
System.out.println("Message received: " + message.getText());
}
429
www.it-ebooks.info
Chapter 13 ■ Messaging
} catch (NamingException | JMSException e) {
e.printStackTrace();
}
}
}
Simplified API
As you can see, the code in Listing 13-2 and 13-3 is quite verbose and low level. You need several artifacts to be able to
produce or consume a message (ConnectionFactory, Connection, Session . . .). On top of that you also need to deal
with the JMSException which is a checked exception (JMSException has several sub classes). This API was created
with JMS 1.1 in 2002 and was not changed until JMS 2.0.
JMS 2.0 introduces a new simplified API, which consists mainly of three new interfaces (JMSContext,
JMSProducer and JMSConsumer). These interfaces rely internally on the ConnectionFactory and other classic APIs but
leave the boilerplate code aside. Thanks to the new JMSRuntimeException, which is an unchecked exception, the code
to send or receive a message is now much easier to write and read (code examples to follow).
Figure 13-9 shows a simplified class diagram of this new API. Note that the legacy, classic, and simplified APIs are
all under the javax.jms package.
Figure 13-9. JMS Simplified API
The simplified API provides the same messaging functionality as the classic API but requires fewer interfaces and
is simpler to use. These main interfaces are:
•
JMSContext: active connection to a JMS provider and a single-threaded context for sending
and receiving messages
•
JMSProducer: object created by a JMSContext that is used for sending messages to a queue or topic
•
JMSConsumer: object created by a JMSContext that is used for receiving messages sent to a
queue or topic
430
www.it-ebooks.info
Chapter 13 ■ Messaging
JMSContext
The JMSContext is the main interface in the simplified JMS API introduced by JMS 2.0. It combines the functionality of
two separate objects from the JMS 1.1 classic API: a Connection (the physical link to the JMS provider) and a Session
(a single-threaded context for sending and receiving messages).
A JMSContext may be created by the application by calling one of several createContext methods on a
ConnectionFactory (see Listing 13-1) and then closed (i.e., application-managed). Alternatively, if the application
is running in a container (EJB or Web), the JMSContext can be injected using the @Inject annotation
(i.e., container-managed).
When an application needs to send messages it uses the createProducer method to create a JMSProducer,
which provides methods to configure and send messages. Messages may be sent either synchronously or
asynchronously. To receive messages, an application can use one of several createConsumer methods to create a
JMSConsumer. Table 13-4 shows you a subset of the JMSContext API.
Table 13-4. Subset of the JMSContext API
Property
Description
void start()
Starts (or restarts) delivery of incoming messages
void stop()
Temporarily stops the delivery of incoming messages
void close()
Closes the JMSContext
void commit()
Commits all messages done in this transaction and
releases any locks currently held
void rollback()
Rolls back any messages done in this transaction and
releases any locks currently held
BytesMessage createBytesMessage()
Creates a BytesMessage object
MapMessage createMapMessage()
Creates a MapMessage object
Message createMessage()
Creates a Message object
ObjectMessage createObjectMessage()
Creates an ObjectMessage object
StreamMessage createStreamMessage()
Creates a StreamMessage object
TextMessage createTextMessage()
Creates a TextMessage object
Topic createTopic(String topicName)
Creates a Topic object
Queue createQueue(String queueName)
Creates a Queue object
JMSConsumer createConsumer(Destination destination)
Creates a JMSConsumer for the specified destination
JMSConsumer createConsumer(Destination destination,
String messageSelector)
Creates a JMSConsumer for the specified destination,
using a message selector
JMSProducer createProducer()
Creates a new JMSProducer object which can be used
to configure and send messages
JMSContext createContext(int sessionMode)
Creates a new JMSContext with the specified
session mode
431
www.it-ebooks.info
Chapter 13 ■ Messaging
JMSProducer
A JMSProducer is used to send messages on behalf of a JMSContext. It provides various send methods to send a
message to a specified destination. An instance of JMSProducer is created by calling the createProducer method on a
JMSContext. It also provides methods to allow send options, message properties and message headers (see Figure 13-8)
to be specified prior to sending the message. Table 13-5 shows a subset of the JMSProducer API.
Table 13-5. Subset of the JMSProducer API
Property
Description
get/set[Type]Property
Sets and returns a message property where [Type] is the type of
the property and can be Boolean, Byte, Double, Float, Int, Long,
Object, Short, String
JMSProducer clearProperties()
Clears any message properties set
Set
Returns an unmodifiable Set view of the names of all the
message properties that have been set
boolean propertyExists(String name)
Indicates whether a message property with the specified name
has been set
get/set[Message Header]
Sets and returns a message header where [Message Header]
can be DeliveryDelay, DeliveryMode, JMSCorrelationID,
JMSReplyTo, JMSType, Priority, TimeToLive
JMSProducer send(Destination destination,
Message message)
Sends a message to the specified destination, using any send options,
message properties and message headers that have been defined
JMSProducer send(Destination destination,
String body)
Sends a TextMessage with the specified body to the specified
destination
JMSConsumer
A JMSConsumer is used to receive messages from a queue or topic. It is created with one of the createConsumer
methods on a JMSContext by passing a Queue or a Topic. As you will later see, a JMSConsumer can be created with a
message selector so it can restrict messages delivered.
A client may receive a message synchronously or asynchronously as they arrive. For asynchronous delivery, a
client can register a MessageListener object with a JMSConsumer. As messages arrive, the provider delivers them by
calling the MessageListener's onMessage method. Table 13-6 shows you a subset of the JMSConsumer API.
432
www.it-ebooks.info
Chapter 13 ■ Messaging
Table 13-6. Subset of the JMSConsumer API
Property
Description
void close()
Closes the JMSConsumer
Message receive()
Receives the next message produced
Message receive(long timeout)
Receives the next message that arrives within the
specified timeout interval
Receives the next message produced and returns its
body as an object of the specified type
Message receiveNoWait()
Receives the next message if one is immediately available
void setMessageListener(MessageListener listener)
Sets the MessageListener
MessageListenergetMessageListener()
Gets the MessageListener
String getMessageSelector()
Gets the message selector expression
Writing Message Producers
The new JMS simplified API allows you to write producers and consumers in a less verbose manner than with the
classic API. But it still needs both of the administered objects: ConnectionFactory and Destination. Depending if
you are running outside or inside a container (EJB, Web or ACC) you will either use JNDI lookups or injection.
As you’ve seen previously, the JMSContext API is the central API to produce and consume messages. If your
application runs outside a container you will need to manage the lifecycle of the JMSContext (by creating and closing
it programmatically). If you run inside a container you can just inject it and leave the container to manage its lifecycle.
Producing a Message outside a Container
A message producer (JMSProducer) is an object created by the JMSContext and is used to send messages to a
destination. The following steps explain how to create a producer that sends a message to a queue (see Listing 13-4)
outside any container (in a pure Java SE environment):
•
Obtain a connection factory and a queue using JNDI lookups
•
Create a JMSContext object using the connection factory (notice the try-with-resources
statement that will automatically close the JMSContext object)
•
Create a javax.jms.JMSProducer using the JSMContext object
•
Send a text message to the queue using the JMSProducer.send() method
Listing 13-4. The Producer Class Produces a Message into a Queue
public class Producer {
public static void main(String[] args) {
try {
// Gets the JNDI context
Context jndiContext = new InitialContext();
433
www.it-ebooks.info