Spring Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

280 lines
10 KiB

[[jms-annotated]]
= Annotation-driven Listener Endpoints
The easiest way to receive a message asynchronously is to use the annotated listener
endpoint infrastructure. In a nutshell, it lets you expose a method of a managed
bean as a JMS listener endpoint. The following example shows how to use it:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(String data) { ... }
}
----
The idea of the preceding example is that, whenever a message is available on the
`jakarta.jms.Destination` `myDestination`, the `processOrder` method is invoked
accordingly (in this case, with the content of the JMS message, similar to
what the xref:integration/jms/receiving.adoc#jms-receiving-async-message-listener-adapter[`MessageListenerAdapter`]
provides).
The annotated endpoint infrastructure creates a message listener container
behind the scenes for each annotated method, by using a `JmsListenerContainerFactory`.
Such a container is not registered against the application context but can be easily
located for management purposes by using the `JmsListenerEndpointRegistry` bean.
TIP: `@JmsListener` is a repeatable annotation on Java 8, so you can associate
several JMS destinations with the same method by adding additional `@JmsListener`
declarations to it.
[[jms-annotated-support]]
== Enable Listener Endpoint Annotations
To enable support for `@JmsListener` annotations, you can add `@EnableJms` to one of
your `@Configuration` classes, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableJms
public class AppConfig {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setDestinationResolver(destinationResolver());
factory.setSessionTransacted(true);
factory.setConcurrency("3-10");
return factory;
}
}
----
By default, the infrastructure looks for a bean named `jmsListenerContainerFactory`
as the source for the factory to use to create message listener containers. In this
case (and ignoring the JMS infrastructure setup), you can invoke the `processOrder`
method with a core poll size of three threads and a maximum pool size of ten threads.
You can customize the listener container factory to use for each annotation or you can
configure an explicit default by implementing the `JmsListenerConfigurer` interface.
The default is required only if at least one endpoint is registered without a specific
container factory. See the javadoc of classes that implement
{api-spring-framework}/jms/annotation/JmsListenerConfigurer.html[`JmsListenerConfigurer`]
for details and examples.
If you prefer xref:integration/jms/namespace.adoc[XML configuration], you can use the `<jms:annotation-driven>`
element, as the following example shows:
[source,xml,indent=0,subs="verbatim,quotes"]
----
<jms:annotation-driven/>
<bean id="jmsListenerContainerFactory"
class="org.springframework.jms.config.DefaultJmsListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="sessionTransacted" value="true"/>
<property name="concurrency" value="3-10"/>
</bean>
----
[[jms-annotated-programmatic-registration]]
== Programmatic Endpoint Registration
`JmsListenerEndpoint` provides a model of a JMS endpoint and is responsible for configuring
the container for that model. The infrastructure lets you programmatically configure endpoints
in addition to the ones that are detected by the `JmsListener` annotation.
The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("anotherQueue");
endpoint.setMessageListener(message -> {
// processing
});
registrar.registerEndpoint(endpoint);
}
}
----
In the preceding example, we used `SimpleJmsListenerEndpoint`, which provides the actual
`MessageListener` to invoke. However, you could also build your own endpoint variant
to describe a custom invocation mechanism.
Note that you could skip the use of `@JmsListener` altogether
and programmatically register only your endpoints through `JmsListenerConfigurer`.
[[jms-annotated-method-signature]]
== Annotated Endpoint Method Signature
So far, we have been injecting a simple `String` in our endpoint, but it can actually
have a very flexible method signature. In the following example, we rewrite it to inject the `Order` with
a custom header:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Component
public class MyService {
@JmsListener(destination = "myDestination")
public void processOrder(Order order, @Header("order_type") String orderType) {
...
}
}
----
The main elements you can inject in JMS listener endpoints are as follows:
* The raw `jakarta.jms.Message` or any of its subclasses (provided that it
matches the incoming message type).
* The `jakarta.jms.Session` for optional access to the native JMS API (for example, for sending
a custom reply).
* The `org.springframework.messaging.Message` that represents the incoming JMS message.
Note that this message holds both the custom and the standard headers (as defined
by `JmsHeaders`).
* `@Header`-annotated method arguments to extract a specific header value, including
standard JMS headers.
* A `@Headers`-annotated argument that must also be assignable to `java.util.Map` for
getting access to all headers.
* A non-annotated element that is not one of the supported types (`Message` or
`Session`) is considered to be the payload. You can make that explicit by annotating
the parameter with `@Payload`. You can also turn on validation by adding an extra
`@Valid`.
The ability to inject Spring's `Message` abstraction is particularly useful to benefit
from all the information stored in the transport-specific message without relying on
transport-specific API. The following example shows how to do so:
[source,java,indent=0,subs="verbatim,quotes"]
----
@JmsListener(destination = "myDestination")
public void processOrder(Message<Order> order) { ... }
----
Handling of method arguments is provided by `DefaultMessageHandlerMethodFactory`, which you can
further customize to support additional method arguments. You can customize the conversion and validation
support there as well.
For instance, if we want to make sure our `Order` is valid before processing it, we can
annotate the payload with `@Valid` and configure the necessary validator, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableJms
public class AppConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(myJmsHandlerMethodFactory());
}
@Bean
public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(myValidator());
return factory;
}
}
----
[[jms-annotated-response]]
== Response Management
The existing support in xref:integration/jms/receiving.adoc#jms-receiving-async-message-listener-adapter[`MessageListenerAdapter`]
already lets your method have a non-`void` return type. When that is the case, the result of
the invocation is encapsulated in a `jakarta.jms.Message`, sent either in the destination specified
in the `JMSReplyTo` header of the original message or in the default destination configured on
the listener. You can now set that default destination by using the `@SendTo` annotation of the
messaging abstraction.
Assuming that our `processOrder` method should now return an `OrderStatus`, we can write it
to automatically send a response, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@JmsListener(destination = "myDestination")
@SendTo("status")
public OrderStatus processOrder(Order order) {
// order processing
return status;
}
----
TIP: If you have several `@JmsListener`-annotated methods, you can also place the `@SendTo`
annotation at the class level to share a default reply destination.
If you need to set additional headers in a transport-independent manner, you can return a
`Message` instead, with a method similar to the following:
[source,java,indent=0,subs="verbatim,quotes"]
----
@JmsListener(destination = "myDestination")
@SendTo("status")
public Message<OrderStatus> processOrder(Order order) {
// order processing
return MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
}
----
If you need to compute the response destination at runtime, you can encapsulate your response
in a `JmsResponse` instance that also provides the destination to use at runtime. We can rewrite the previous
example as follows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@JmsListener(destination = "myDestination")
public JmsResponse<Message<OrderStatus>> processOrder(Order order) {
// order processing
Message<OrderStatus> response = MessageBuilder
.withPayload(status)
.setHeader("code", 1234)
.build();
return JmsResponse.forQueue(response, "status");
}
----
Finally, if you need to specify some QoS values for the response such as the priority or
the time to live, you can configure the `JmsListenerContainerFactory` accordingly,
as the following example shows:
[source,java,indent=0,subs="verbatim,quotes"]
----
@Configuration
@EnableJms
public class AppConfig {
@Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
QosSettings replyQosSettings = new QosSettings();
replyQosSettings.setPriority(2);
replyQosSettings.setTimeToLive(10000);
factory.setReplyQosSettings(replyQosSettings);
return factory;
}
}
----