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
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; |
|
} |
|
} |
|
---- |
|
|
|
|
|
|
|
|