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>