There are many things to consider when designing an enterprise
application to use the JMS APIs 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 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 exception.
- 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.
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.
- To improve performance, the application server pools connections
and sessions with the JMS provider. You have to configure
the connection and session pool properties appropriately for your
applications, otherwise you might 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 exception.
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 have 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 activation specification. 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 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 parameter prevents
the delivery of messages published on the same connection as the durable
subscriber.
Applications can use a two argument form of
the
createDurableSubscriber operation 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, 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 or 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. For example, a servlet
and a message-driven bean are deployed in the same server. When this
servlet dispatches a request it sends a message to a queue that is
serviced by the message-driven bean (that is, messages produced by
the servlet are consumed by the message-driven bean onMessage() method).
The servlet subsequently issues a receive(), waiting
for a reply on a temporary ReplyTo queue. The message-driven bean 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 then enters a condition where no threads remain to process
any of the message-driven beans that are now pending. Because the
servlets are waiting in blocking receives, 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 from the producer application component.
Although worker thread usage might still have 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 can 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 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.