spring-framework/framework-docs/modules/ROOT/pages/integration/jms/receiving.adoc

189 lines
9.1 KiB
Plaintext

[[jms-receiving]]
= Receiving a Message
This describes how to receive messages with JMS in Spring.
[[jms-receiving-sync]]
== Synchronous Receipt
While JMS is typically associated with asynchronous processing, you can
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 `receiveTimeout` property specifies how long
the receiver should wait before giving up waiting for a message.
[[jms-receiving-async]]
== Asynchronous Receipt: Message-Driven POJOs
NOTE: Spring also supports annotated-listener endpoints through the use of the `@JmsListener`
annotation and provides open infrastructure to register endpoints programmatically.
This is, by far, the most convenient way to set up an asynchronous receiver.
See xref:integration/jms/annotated.adoc#jms-annotated-support[Enable Listener Endpoint Annotations] for more details.
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
xref:integration/jms/receiving.adoc#jms-receiving-async-message-listener-adapter[Using `MessageListenerAdapter`])
on an MDP is that it must implement the `jakarta.jms.MessageListener` interface.
Note that, if your POJO receives messages on multiple threads, it is important to
ensure that your implementation is thread-safe.
The following example shows a simple implementation of an MDP:
include-code::./ExampleListener[tag=snippet,indent=0]
Once you have implemented your `MessageListener`, it is time to create a message listener
container.
The following example shows how to define and configure one of the message listener
containers that ships with Spring (in this case, `DefaultMessageListenerContainer`):
include-code::./JmsConfiguration[tag=snippet,indent=0]
See the Spring javadoc of the various message listener containers (all of which implement
{spring-framework-api}/jms/listener/MessageListenerContainer.html[MessageListenerContainer])
for a full description of the features supported by each implementation.
[[jms-receiving-async-session-aware-message-listener]]
== Using the `SessionAwareMessageListener` Interface
The `SessionAwareMessageListener` interface is a Spring-specific interface that provides
a similar contract to the JMS `MessageListener` interface but also gives the message-handling
method access to the JMS `Session` from which the `Message` was received.
The following listing shows the definition of the `SessionAwareMessageListener` interface:
[source,java,indent=0,subs="verbatim,quotes",chomp="-packages"]
----
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 (by using the `Session` supplied in the `onMessage(Message, Session)`
method). All of the message listener container implementations that ship with 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.
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 thrown exceptions.
[[jms-receiving-async-message-listener-adapter]]
== Using `MessageListenerAdapter`
The `MessageListenerAdapter` class is the final component in Spring's asynchronous
messaging support. In a nutshell, it lets you expose almost any class as an MDP
(though there are some constraints).
Consider the following interface definition:
include-code::./MessageDelegate[tag=snippet,indent=0]
Notice that, although the interface extends neither the `MessageListener` nor the
`SessionAwareMessageListener` interface, you can still use it as an MDP by using 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.
Now consider the following implementation of the `MessageDelegate` interface:
include-code::./DefaultMessageDelegate[tag=snippet,indent=0]
In particular, note how the preceding implementation of the `MessageDelegate` interface (the
`DefaultMessageDelegate` class) has no JMS dependencies at all. It truly is a
POJO that we can make into an MDP through the following configuration:
include-code::./JmsConfiguration[tag=snippet,indent=0]
The next example shows another MDP that can handle only receiving 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 can see later in this section). Notice
also how the `receive(..)` method is strongly typed to receive and respond only to JMS
`TextMessage` messages.
The following listing shows the definition of the `TextMessageDelegate` interface:
include-code::./TextMessageDelegate[tag=snippet,indent=0]
The following listing shows a class that implements the `TextMessageDelegate` interface:
include-code::./DefaultTextMessageDelegate[tag=snippet,indent=0]
The configuration of the attendant `MessageListenerAdapter` would then be as follows:
include-code::./MessageListenerConfiguration[tag=snippet,indent=0]
Note that, if the `messageListener` receives a JMS `Message` of a type
other than `TextMessage`, an `IllegalStateException` is 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 following interface and class:
include-code::./ResponsiveTextMessageDelegate[tag=snippet,indent=0]
include-code::./DefaultResponsiveTextMessageDelegate[tag=snippet,indent=0]
If you use the `DefaultResponsiveTextMessageDelegate` in conjunction with a
`MessageListenerAdapter`, any non-null value that is returned from the execution of
the `'receive(..)'` method is (in the default configuration) converted into a
`TextMessage`. The resulting `TextMessage` is then 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, an `InvalidDestinationException` is thrown
(note that this exception is not swallowed and propagates up the
call stack).
[[jms-tx-participation]]
== Processing Messages Within Transactions
Invoking a message listener within a transaction requires only reconfiguration of the
listener container.
You can activate local resource transactions through the `sessionTransacted` flag
on the listener container definition. Each message listener invocation then operates
within an active JMS transaction, with message receipt rolled back in case of listener
execution failure. Sending a response message (through `SessionAwareMessageListener`) is
part of the same local transaction, but any other resource operations (such as
database access) operate independently. This usually requires duplicate message
detection in the listener implementation, to cover the case where database processing
has committed but message processing failed to commit.
Consider the following bean definition:
include-code::./JmsConfiguration[tag=snippet,indent=0]
To participate in an externally managed transaction, you need to configure a
transaction manager and use a listener container that supports externally managed
transactions (typically, `DefaultMessageListenerContainer`).
To configure a message listener container for XA transaction participation, you want
to configure a `JtaTransactionManager` (which, by default, delegates to the Jakarta 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
Jakarta EE server's configuration of JNDI resources.) This lets message receipt as well
as (for example) database access be part of the same transaction (with unified commit
semantics, at the expense of XA transaction log overhead).
The following bean definition creates a transaction manager:
include-code::./ExternalTxJmsConfiguration[tag=transactionManagerSnippet,indent=0]
Then we need to add it to our earlier container configuration. The container
takes care of the rest. The following example shows how to do so:
include-code::./ExternalTxJmsConfiguration[tag=jmsContainerSnippet,indent=0]