While JMS is typically associated with asynchronous processing, it
is possible to consume messages synchronously. The overloaded
receive(..)
methods provide this functionality.
During a synchronous receive, the calling thread blocks until a message
becomes available. This can be a dangerous operation since the calling
thread can potentially be blocked indefinitely. The property
receiveTimeout specifies how long the receiver
should wait before giving up waiting for a message.
In a fashion similar to a Message-Driven Bean (MDB) in the EJB
world, the Message-Driven POJO (MDP) acts as a receiver for JMS
messages. The one restriction (but see also below for the discussion of
the MessageListenerAdapter
class) on an MDP is
that it must implement the
javax.jms.MessageListener
interface.
Please also be aware that in the case where your POJO will be receiving
messages on multiple threads, it is important to ensure that your
implementation is thread-safe.
Below is a simple implementation of an MDP:
import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.TextMessage; public class ExampleListener implements MessageListener { public void onMessage(Message message) { if (message instanceof TextMessage) { try { System.out.println(((TextMessage) message).getText()); } catch (JMSException ex) { throw new RuntimeException(ex); } } else { throw new IllegalArgumentException("Message must be of type TextMessage"); } } }
Once you've implemented your
MessageListener
, it's time to create a
message listener container.
Find below an example of how to define and configure one of the
message listener containers that ships with Spring (in this case the
DefaultMessageListenerContainer
).
<!-- this is the Message Driven POJO (MDP) --> <bean id="messageListener" class="jmsexample.ExampleListener" /> <!-- and this is the message listener container --> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener" /> </bean>
Please refer to the Spring Javadoc of the various message listener containers for a full description of the features supported by each implementation.
The SessionAwareMessageListener
interface is a Spring-specific interface that provides a similar
contract the JMS MessageListener
interface, but also provides the message handling method with access to
the JMS Session
from which the
Message
was received.
package org.springframework.jms.listener; public interface SessionAwareMessageListener { void onMessage(Message message, Session session) throws JMSException; }
You can choose to have your MDPs implement this interface (in
preference to the standard JMS
MessageListener
interface) if you want
your MDPs to be able to respond to any received messages (using the
Session
supplied in the
onMessage(Message, Session)
method). All of the
message listener container implementations that ship wth Spring have
support for MDPs that implement either the
MessageListener
or
SessionAwareMessageListener
interface.
Classes that implement the
SessionAwareMessageListener
come with the
caveat that they are then tied to Spring through the interface. The
choice of whether or not to use it is left entirely up to you as an
application developer or architect.
Please note that the 'onMessage(..)'
method of
the SessionAwareMessageListener
interface
throws JMSException
. In contrast to the standard
JMS MessageListener
interface, when using
the SessionAwareMessageListener
interface, it is the responsibility of the client code to handle any
exceptions thrown.
The MessageListenerAdapter
class is the
final component in Spring's asynchronous messaging support: in a
nutshell, it allows you to expose almost any class
as a MDP (there are of course some constraints).
Consider the following interface definition. Notice that although
the interface extends neither the
MessageListener
nor
SessionAwareMessageListener
interfaces,
it can still be used as a MDP via the use of the
MessageListenerAdapter
class. Notice also how the
various message handling methods are strongly typed according to the
contents of the various
Message
types that they can receive and
handle.
public interface MessageDelegate { void handleMessage(String message); void handleMessage(Map message); void handleMessage(byte[] message); void handleMessage(Serializable message); }
public class DefaultMessageDelegate implements MessageDelegate { // implementation elided for clarity... }
In particular, note how the above implementation of the
MessageDelegate
interface (the above
DefaultMessageDelegate
class) has
no JMS dependencies at all. It truly is a POJO that
we will make into an MDP via the following configuration.
<!-- this is the Message Driven POJO (MDP) --> <bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="jmsexample.DefaultMessageDelegate"/> </constructor-arg> </bean> <!-- and this is the message listener container... --> <bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener" /> </bean>
Below is an example of another MDP that can only handle the
receiving of JMS TextMessage
messages.
Notice how the message handling method is actually called
'receive'
(the name of the message handling method in
a MessageListenerAdapter
defaults to
'handleMessage'
), but it is configurable (as you will
see below). Notice also how the 'receive(..)'
method
is strongly typed to receive and respond only to JMS
TextMessage
messages.
public interface TextMessageDelegate { void receive(TextMessage message); }
public class DefaultTextMessageDelegate implements TextMessageDelegate { // implementation elided for clarity... }
The configuration of the attendant
MessageListenerAdapter
would look like
this:
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="jmsexample.DefaultTextMessageDelegate"/> </constructor-arg> <property name="defaultListenerMethod" value="receive"/> <!-- we don't want automatic message context extraction --> <property name="messageConverter"> <null/> </property> </bean>
Please note that if the above 'messageListener'
receives a JMS Message
of a type other
than TextMessage
, an
IllegalStateException
will be thrown (and
subsequently swallowed). Another of the capabilities of the
MessageListenerAdapter
class is the ability to
automatically send back a response
Message
if a handler method returns a
non-void value. Consider the interface and class:
public interface ResponsiveTextMessageDelegate { // notice the return type... String receive(TextMessage message); }
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate { // implementation elided for clarity... }
If the above
DefaultResponsiveTextMessageDelegate
is used in
conjunction with a MessageListenerAdapter
then
any non-null value that is returned from the execution of the
'receive(..)'
method will (in the default
configuration) be converted into a
TextMessage
. The resulting
TextMessage
will then be sent to the
Destination
(if one exists) defined in
the JMS Reply-To property of the original
Message
, or the default
Destination
set on the
MessageListenerAdapter
(if one has been
configured); if no Destination
is found
then an InvalidDestinationException
will be
thrown (and please note that this exception will
not be swallowed and will propagate up
the call stack).
Invoking a message listener within a transaction only requires reconfiguration of the listener container.
Local resource transactions can simply be activated through the
sessionTransacted
flag on the listener container
definition. Each message listener invocation will then operate within an
active JMS transaction, with message reception rolled back in case of
listener execution failure. Sending a response message (via
SessionAwareMessageListener
) will be part
of the same local transaction, but any other resource operations (such
as database access) will operate independently. This usually requires
duplicate message detection in the listener implementation, covering the
case where database processing has committed but message processing
failed to commit.
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener"/> <property name="sessionTransacted" value="true"/> </bean>
For participating in an externally managed transaction, you will
need to configure a transaction manager and use a listener container
which supports externally managed transactions: typically
DefaultMessageListenerContainer
.
To configure a message listener container for XA transaction
participation, you'll want to configure a
JtaTransactionManager
(which, by default,
delegates to the Java EE server's transaction subsystem). Note that the
underlying JMS ConnectionFactory needs to be XA-capable and properly
registered with your JTA transaction coordinator! (Check your Java EE
server's configuration of JNDI resources.) This allows message recepton
as well as e.g. database access to be part of the same transaction (with
unified commit semantics, at the expense of XA transaction log
overhead).
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
Then you just need to add it to our earlier container configuration. The container will take care of the rest.
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destination" ref="destination"/> <property name="messageListener" ref="messageListener"/> <property name="transactionManager" ref="transactionManager"/> </bean>