This topic describes things to consider when designing an enterprise
application to use the JMS API directly for asynchronous messaging.
- For messaging operations, you should write application programs
that use only references to the interfaces defined in Sun's javax.jms package.
JMS defines a generic view of a messaging that maps onto the underlying
transport. An enterprise application that uses JMS makes use of the following
interfaces that are defined in Sun's javax.jms package:
- Connection
- Provides access to the underlying transport, and is used to create Sessions.
- Session
- Provides a context for producing and consuming messages, including the
methods used to create MessageProducers and MessageConsumers.
- MessageProducer
- Used to send messages.
- MessageConsumer
- Used to receive messages.
The generic JMS interfaces are subclassed into the following
more specific versions for Point-to-Point and Publish/Subscribe behavior.
Table 1. The Point-to-Point and Publish/Subscribe versions of JMS common interfaces
JMS
common interfaces |
Point-to-Point interfaces |
Publish/Subscribe interfaces |
ConnectionFactory |
QueueConnectionFactory |
TopicConnectionFactory |
Connection |
QueueConnection |
TopicConnection |
Destination |
Queue |
Topic |
Session |
QueueSession, |
TopicSession, |
MessageProducer |
QueueSender |
TopicPublisher |
MessageConsumer |
QueueReceiver,
QueueBrowser
|
TopicSubscriber |
For more information about using these JMS interfaces, see
the Java Message Service Documentation and
the WebSphere MQ Using Java book, SC34-5456.
The
section
"Java Message Service (JMS) Requirements" of the
J2EE
specification gives a list of methods that must not be called in Web
and EJB containers:
javax.jms.Session method setMessageListener
javax.jms.Session method getMessageListener
javax.jms.Session method run
javax.jms.QueueConnection method createConnectionConsumer
javax.jms.TopicConnection method createConnectionConsumer
javax.jms.TopicConnection method createDurableConnectionConsumer
javax.jms.MessageConsumer method getMessageListener
javax.jms.MessageConsumer method setMessageListener
javax.jms.Connection setExceptionListener
javax.jms.Connection stop
javax.jms.Connection setClientID
This method restriction is enforced in IBM WebSphere Application
Server by throwing a javax.jms.IllegalStateException.
- Applications refer to JMS resources that are predefined, as administered
objects, to WebSphere Application Server.
Details of JMS resources
that are used by enterprise applications are defined to WebSphere Application
Server and bound into the JNDI namespace by the WebSphere administrative support.
An enterprise application can retrieve these objects from the JNDI namespace
and use them without needing to know anything about their implementation.
This enables the underlying messaging architecture defined by the JMS resources
to be changed without requiring changes to the enterprise application. When
designing an enterprise application, you need to identify the details of the
following types of JMS resources.
Table 2. JMS resources for Point-to-Point and Publish/Subscribe messaging
Point-to-Point |
Publish/Subscribe |
ConnectionFactory (or QueueConnectionFactory)
Queue
|
ConnectionFactory (or TopicConnectionFactory)
Topic
|
A connection factory is used to create connections from the JMS
provider to the messaging system, and encapsulates the configuration parameters
needed to create connections.
For more information about the properties
of these JMS resources, see Configuring JMS provider resources.
- To improve performance, the application server pools connections
and sessions with the JMS provider. You need to configure the connection
and session pool properties appropriately for your applications, otherwise
you may not get the connection and session behavior that you want.
- Applications must not cache JMS connections, sessions, producers
or consumers. WebSphere Application Server closes these objects
when a bean or servlet completes, and so any attempt to use a cached object
will fail with a javax.jms.IllegalStateException.
To improve performance,
applications can cache JMS objects that have been looked up from JNDI. For
example, an EJB or servlet needs to look up a JMS ConnectionFactory only once,
but it must call the createConnection method on each instantiation. Because
of the effect of pooling on connections and sessions with the JMS provider,
there should be no performance impact.
- A non-durable subscriber can only be used in the same transactional
context (for example, a global transaction or an unspecified transaction context)
that existed when the subscriber was created. For more information
about this context restriction, see The
effect of transaction context on non-durable subscribers.
- Using durable subscriptions with the default messaging provider.
A durable subscription on a JMS topic enables a subscriber to receive
a copy of all messages published to that topic, even after periods of time
when the subscriber is not connected to the server. Therefore, subscriber
applications can operate disconnected from the server for long periods of
time, and then reconnect to the server and process messages that were published
during their absence. If an application creates a durable subscription, it
is added to the runtime list that administrators can display and act on through
the administrative console.
Each durable subscription is given a unique
identifier,
clientID##subName where:
- clientID
- The client identifier used to associate a connection and its objects with
the messages maintained for applications (as clients of the JMS provider).
You should use a naming convention that helps you identify the applications,
in case you need to relate durable subscriptions to the associated applications
for runtime administration.
- subName
- The subscription name used to uniquely identify a durable subscription
within a given client identifier.
For durable subscriptions created by message-driven
beans, these values are set on the JMS activationSpec. For other durable subscriptions,
the client identifier is set on the JMS connection factory, and the subscription
name is set by the application on the createDurableSubscriber operation.
To
create a durable subscription to a topic, an application uses the createDurableSubscriber
operation defined in the JMS API:
public TopicSubscriber createDurableSubscriber(Topic topic,
java.lang.String subName,
java.lang.String messageSelector,
boolean noLocal)
throws JMSException
- topic
- The name of the JMS topic to subscribe to. This is the name of an object
supporting the javax.jms.Topic interfaces, such as is found by looking up
a suitable JNDI entry.
- subName
- The name used to identify this subscription.
- messageSelector
- Only messages with properties matching the message selector expression
are delivered to consumers. A value of null or an empty string indicates that
all messages should be delivered.
- noLocal
- If set to true, this prevents the delivery of messages published on the
same connection as the durable subscriber.
Applications can use a two argument form of createDurableSubscriber
that takes only topic and subName parameters. This alternative call directly
invokes the four argument version shown above, but sets messageSelector to
null (so all messages are delivered) and sets noLocal to false (so messages
published on the connection are delivered). For example, to create a durable
subscription to the topic called myTopic, with the subscription name of mySubscription:
session.createDurableSubscriber(myTopic,"mySubscription");
If the createDurableSubscription operation fails, it throws a JMS
exception that provides a message and linked exception to give more detail
about the cause of the problem.
To delete a durable subscription, an
application uses the unsubscribe operation defined in the JMS API
In
normal operation there can be at most one active (connected) subscriber for
a durable subscription at a time. However, the subscriber application can
be running in a cloned application server, for failover and load balancing
purposes. In this case the "one active subscriber" restriction is lifted
to provide a shared durable subscription that can have multiple simultaneous
consumers.
For more information about application use of durable subscriptions,
see the section "Using Durable Subscriptions" in the JMS specification.
- Decide what message selectors are needed. You can use
the JMS message selector mechanism to select a subset of the messages on a
queue so that this subset is returned by a receive call. The selector can
refer to fields in the JMS message header and fields in the message properties.
- Acting on messages received. When a message is received,
you can act on it as needed by the business logic of the application. Some
general JMS actions are to check that the message is of the correct type and
extract the content of the message. To extract the content from the body of
the message, you need to cast from the generic Message class (which is the
declared return type of the receive methods) to the more specific subclass,
such as TextMessage. It is good practice always to test the message class
before casting, so that unexpected errors can be handled gracefully.
In
this example, the instanceof operator is used to check that the message received
is of the TextMessage type. The message content is then extracted by casting
to the TextMessage subclass.
if ( inMessage instanceof TextMessage )
...
String replyString = ((TextMessage) inMessage).getText();
- JMS applications using the default messaging provider can access,
without any restrictions, the content of messages that have been received
from WebSphere Application Server Version 5 embedded messaging or WebSphere
MQ.
- JMS applications can access the full set of JMS_IBM* properties.
These properties are of value to JMS applications that use resources
provided by the default messaging provider, the V5 default messaging provider,
or the WebSphere MQ provider.
For messages handled by WebSphere MQ, the
JMS_IBM* properties are mapped to equivalent WebSphere MQ Message Descriptor
(MQMD) fields. For more information about the JMS_IBM* properties and MQMD
fields, see the WebSphere MQ: Using Java book, SC34-6066.
- JMS applications can use report messages as a form of managed request/response
processing, to give remote feedback to producers on the outcome of their send
operations and the fate of their messages. JMS applications can
request a full range of report options using JMS_IBM_Report_Xxxx message properties.
For more information about using JMS report messages, see JMS report messages.
- JMS applications can use the JMS_IBM_Report_Discard_Msg property
to control how a request message is disposed of if it cannot be delivered
to the destination queue.
- MQRO_Dead_Letter_Queue
- This is the default. The request message should be written to the dead
letter queue.
- MQRO_Discard
- The request message should be discarded. This is usually used in conjunction
with MQRO_Exception_With_Full_Data to return an undeliverable request message
to its sender.
- Using a listener to receive messages asynchronously. In
a client, not in a servlet or enterprise bean, an alternative to making calls
to QueueReceiver.receive() is to register a method that is called automatically
when a suitable message is available; for example:
...
MyClass listener =new MyClass();
queueReceiver.setMessageListener(listener);
//application continues with other application-specific behavior.
...
When a message is available, it is retrieved by the onMessage()
method on the listener object.
import javax.jms.*;
public class MyClass implements MessageListener
{
public void onMessage(Message message)
{
System.out.println("message is "+message);
//application specific processing here
...
}
}
For asynchronous message delivery, the application code cannot catch exceptions
raised by failures to receive messages. This is because the application code
does not make explicit calls to receive() methods. To cope with this situation,
you can register an ExceptionListener, which is an instance of a class that
implements the onException()method. When an error occurs, this method is called
with the JMSException passed as its only parameter.
For more details
about using listeners to receive messages asynchronously, see the Java
Message Service Documentation.
Take care when performing a JMS receive() from a
server-side application component if that receive() invocation is waiting
on a message produced by another application component that is deployed in
the same server. Such a JMS receive() is synchronous, so blocks
until the response message is received.This type of application design
can lead to the consumer/producer problem where the entire set of work threads
can be exhausted by the receiving component, which has been blocked waiting
for responses, leaving no available worker thread for which to dispatch the
application component that would generate the response JMS message.
To
illustrate this problem, picture a servlet and a message-driven bean deployed
in the same server. When this servlet dispatches a request it sends a message
to a queue which is serviced by the message-driven bean (that is, messages
produced by the servlet are consumed by the message-driven bean's onMessage()
method). The servlet subsequently issues a receive(), waiting for a reply
on a temporary ReplyTo queue. The message-driven bean's onMessage() method
performs a database query and sends back a reply to the servlet on the temporary
queue. If a large number of servlet requests occur at once (relative to the
number of server worker threads), then all available server
worker threads will probably be used to dispatch a servlet request, send a message,
and wait for a reply. The application server enters a deadly-embrace condition
whereby no threads remain to process any of the message-driven beans that
are now pending. Since the servlets are waiting in blocking recieves, the
server hangs, likely leading to application failure.
Possible solutions
are:
- Ensure that the number of worker threads (# of threads per server region
* # of server regions per server) exceeds the number of concurrent dispatches
of the application component doing the receive() so that there is always a
worker thread available to dispatch the message producing component.
- Use an application topology that places the receiver application component
in a separate server than the producer application component. While worker
thread usage can still need to be carefully considered under such a deployment
scenario, this separation ensures that there are always be threads that cannot
be blocked by the message receiving component. There can be other interactions
to consider, such as an application server that has multiple applications
installed.
- Refactor your application to do the message receives from a client component,
which will not compete with the producer component for worker threads. Furthermore,
the client component can do asynchronous (non-blocking) receives, which are
prohibited from J2EE servers. So, for example, the example application above
could be refactored to have a client sending messages to a queue and then
waiting for a response from the MDB.
- If you want to use authentication with WebSphere MQ or the Version
5 Embedded Messaging support, you cannot have user IDs longer than 12 characters.
For example, the default Windows NT user ID, administrator, is
not valid for use with WebSphere internal messaging, because it contains 13
characters.
- The following points, as defined in the EJB specification, apply
to the use of flags on createxxxSession calls:
- The transacted flag passed on createxxxSession is ignored inside
a global transaction and all work is performed as part of the transaction.
Outside of a transaction the transacted flag is used and, if set to true,
the application should use session.commit() and session.rollback() to control
the completion of the work. In an EJB2.0 module, if the transacted flag is
set to true and outside of an XA transaction, then the session is involved
in the WebSphere local transaction and the unresolved action attribute of
the method applies to the JMS work if it is not committed or rolled back by
the application.
- Clients cannot use using Message.acknowledge() to acknowledge messages.
If a value of CLIENT_ACKNOWLEDGE is passed on the createxxxSession
call, then messages are automatically acknowledged by the application server
and Message.acknowledge() is not used.
- If you want your application to use WebSphere MQ as an external
JMS provider, then send messages within a container-managed transaction.
When you use WebSphere MQ as an external JMS provider, messages sent
within a user-managed transaction can arrive before the transaction commits.
This occurs only when you use WebSphere MQ as an external JMS provider, and
you send messages to a WebSphere MQ queue within a user-managed transaction.
The message arrives on the destination queue before the transaction commits.
The
cause of this problem is that the WebSphere MQ resource manager has not been
enlisted in the user-managed transaction.
The solution is to use a container-managed
transaction.