* GH-8014: Improve doc for Service Activator Fixes https://github.com/spring-projects/spring-integration/issues/8014 * Fix language in Docs Co-authored-by: Gary Russell <grussell@vmware.com> --------- Co-authored-by: Gary Russell <grussell@vmware.com>
250 lines
13 KiB
Plaintext
250 lines
13 KiB
Plaintext
[[service-activator]]
|
|
=== Service Activator
|
|
|
|
The service activator is the endpoint type for connecting any Spring-managed object to an input channel so that it may play the role of a service.
|
|
If the service produces output, it may also be connected to an output channel.
|
|
Alternatively, an output-producing service may be located at the end of a processing pipeline or message flow, in which case the inbound message's `replyChannel` header can be used.
|
|
This is the default behavior if no output channel is defined.
|
|
As with most of the configuration options described here, the same behavior actually applies for most of the other components.
|
|
|
|
The service activator is essentially a generic endpoint for calling a method on some object with an input message (payload and headers).
|
|
Its internal logic is based on a `MessageHandler` which can be any possible implementation for a specific use-case, for example `DefaultMessageSplitter`, `AggregatingMessageHandler`, `SftpMessageHandler`, `JpaOutboundGateway` etc.
|
|
Therefore, any outbound gateway and outbound channel adapter mentioned in this reference manual should be treated as a specific extension of this service activator endpoint; they all, in the end, call some object's method.
|
|
|
|
[[service-activator-namespace]]
|
|
==== Configuring Service Activator
|
|
|
|
With Java & Annotation configuration, it is sufficient to mark the respective service method with the `@ServiceActivator` annotation - and the framework calls it when messages are consumed from an input channel:
|
|
|
|
====
|
|
[source,java]
|
|
----
|
|
public class SomeService {
|
|
|
|
@ServiceActivator(inputChannel = "exampleChannel")
|
|
public void exampleHandler(SomeData payload) {
|
|
...
|
|
}
|
|
|
|
}
|
|
----
|
|
====
|
|
|
|
See more information in the <<./configuration.adoc#annotations, Annotation Support>>.
|
|
|
|
For Java, Groovy or Kotlin DSLs, the `.handle()` operator of an `IntegrationFlow` represents a service activator:
|
|
|
|
====
|
|
[source, java, role="primary"]
|
|
.Java DSL
|
|
----
|
|
@Bean
|
|
public IntegrationFlow someFlow() {
|
|
return IntegrationFlow
|
|
.from("exampleChannel")
|
|
.handle(someService, "exampleHandler")
|
|
.get();
|
|
}
|
|
----
|
|
[source, kotlin, role="secondary"]
|
|
.Kotlin DSL
|
|
----
|
|
@Bean
|
|
fun someFlow() =
|
|
integrationFlow("exampleChannel") {
|
|
handle(someService, "exampleHandler")
|
|
}
|
|
----
|
|
[source, groovy, role="secondary"]
|
|
.Groovy DSL
|
|
----
|
|
@Bean
|
|
someFlow() {
|
|
integrationFlow 'exampleChannel',
|
|
{
|
|
handle someService, 'exampleHandler'
|
|
}
|
|
}
|
|
----
|
|
====
|
|
|
|
See more information about the DSLs in the respective chapters:
|
|
|
|
* <<./dsl.adoc#java-dsl,Java DSL>>
|
|
* <<./kotlin-dsl.adoc#kotlin-dsl,Kotlin DSL>>
|
|
* <<./groovy-dsl.adoc#groovy-dsl,Groovy DSL>>
|
|
|
|
To create a service activator when using XML configuration, use the 'service-activator' element with the 'input-channel' and 'ref' attributes, as the following example shows:
|
|
|
|
====
|
|
[source,xml]
|
|
----
|
|
<int:service-activator input-channel="exampleChannel" ref="exampleHandler"/>
|
|
----
|
|
====
|
|
|
|
The preceding configuration selects all the methods from the `exampleHandler` that meet one of the messaging requirements, which are as follows:
|
|
|
|
* annotated with `@ServiceActivator`
|
|
* is `public`
|
|
* not return `void` if `requiresReply == true`
|
|
|
|
The target method for invocation at runtime is selected for each request message by their `payload` type or as a fallback to the `Message<?>` type if such a method is present on target class.
|
|
|
|
Starting with version 5.0, one service method can be marked with the `@org.springframework.integration.annotation.Default` as a fallback for all non-matching cases.
|
|
This can be useful when using <<./endpoint.adoc#content-type-conversion, content-type conversion>> with the target method being invoked after conversion.
|
|
|
|
To delegate to an explicitly defined method of any object, you can add the `method` attribute, as the following example shows:
|
|
|
|
====
|
|
[source,xml]
|
|
----
|
|
<int:service-activator input-channel="exampleChannel" ref="somePojo" method="someMethod"/>
|
|
----
|
|
====
|
|
|
|
In either case, when the service method returns a non-null value, the endpoint tries to send the reply message to an appropriate reply channel.
|
|
To determine the reply channel, it first checks whether an `output-channel` was provided in the endpoint configuration, as the following example shows:
|
|
|
|
====
|
|
[source,xml]
|
|
----
|
|
<int:service-activator input-channel="exampleChannel" output-channel="replyChannel"
|
|
ref="somePojo" method="someMethod"/>
|
|
----
|
|
====
|
|
|
|
If the method returns a result and no `output-channel` is defined, the framework then checks the request message's `replyChannel` header value.
|
|
If that value is available, it then checks its type.
|
|
If it is a `MessageChannel`, the reply message is sent to that channel.
|
|
If it is a `String`, the endpoint tries to resolve the channel name to a channel instance.
|
|
If the channel cannot be resolved, a `DestinationResolutionException` is thrown.
|
|
If it can be resolved, the message is sent there.
|
|
If the request message does not have a `replyChannel` header and the `reply` object is a `Message`, its `replyChannel` header is consulted for a target destination.
|
|
This is the technique used for request-reply messaging in Spring Integration, and it is also an example of the return address pattern.
|
|
|
|
If your method returns a result, and you want to discard it and end the flow, you should configure the `output-channel` to send to a `NullChannel`.
|
|
For convenience, the framework registers one with the name, `nullChannel`.
|
|
See <<./channel.adoc#channel-special-channels,Special Channels>> for more information.
|
|
|
|
The service activator is one of those components that is not required to produce a reply message.
|
|
If your method returns `null` or has a `void` return type, the service activator exits after the method invocation, without any signals.
|
|
This behavior can be controlled by the `AbstractReplyProducingMessageHandler.requiresReply` option, which is also exposed as `requires-reply` when configuring with the XML namespace.
|
|
If the flag is set to `true` and the method returns null, a `ReplyRequiredException` is thrown.
|
|
|
|
The argument in the service method could be either a message or an arbitrary type.
|
|
If the latter, then it is assumed to be a message payload, which is extracted from the message and injected into the service method.
|
|
We generally recommend this approach, as it follows and promotes a POJO model when working with Spring Integration.
|
|
Arguments may also have `@Header` or `@Headers` annotations, as described in <<./configuration.adoc#annotations,Annotation Support>>.
|
|
|
|
NOTE: The service method is not required to have any arguments, which means you can implement event-style service activators (where all you care about is an invocation of the service method) and not worry about the contents of the message.
|
|
Think of it as a null JMS message.
|
|
An example use case for such an implementation is a simple counter or monitor of messages deposited on the input channel.
|
|
|
|
Starting with version 4.1, the framework correctly converts message properties (`payload` and `headers`) to the Java 8 `Optional` POJO method parameters, as the following example shows:
|
|
|
|
====
|
|
[source,java]
|
|
----
|
|
public class MyBean {
|
|
public String computeValue(Optional<String> payload,
|
|
@Header(value="foo", required=false) String foo1,
|
|
@Header(value="foo") Optional<String> foo2) {
|
|
if (payload.isPresent()) {
|
|
String value = payload.get();
|
|
...
|
|
}
|
|
else {
|
|
...
|
|
}
|
|
}
|
|
|
|
}
|
|
----
|
|
====
|
|
|
|
We generally recommend using a `ref` attribute if the custom service activator handler implementation can be reused in other `<service-activator>` definitions.
|
|
However, if the custom service activator handler implementation is only used within a single definition of the `<service-activator>`, you can provide an inner bean definition, as the following example shows:
|
|
|
|
====
|
|
[source,xml]
|
|
----
|
|
<int:service-activator id="exampleServiceActivator" input-channel="inChannel"
|
|
output-channel = "outChannel" method="someMethod">
|
|
<beans:bean class="org.something.ExampleServiceActivator"/>
|
|
</int:service-activator>
|
|
----
|
|
====
|
|
|
|
NOTE: Using both the `ref` attribute and an inner handler definition in the same `<service-activator>` configuration is not allowed, as it creates an ambiguous condition and results in an exception being thrown.
|
|
|
|
IMPORTANT: If the `ref` attribute references a bean that extends `AbstractMessageProducingHandler` (such as handlers provided by the framework itself), the configuration is optimized by injecting the output channel into the handler directly.
|
|
In this case, each `ref` must be to a separate bean instance (or a `prototype`-scoped bean) or use the inner `<bean/>` configuration type.
|
|
If you inadvertently reference the same message handler from multiple beans, you get a configuration exception.
|
|
|
|
===== Service Activators and the Spring Expression Language (SpEL)
|
|
|
|
Since Spring Integration 2.0, service activators can also benefit from https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions[SpEL].
|
|
|
|
For example, you can invoke any bean method without pointing to the bean in a `ref` attribute or including it as an inner bean definition, as follows:
|
|
|
|
====
|
|
[source,xml]
|
|
----
|
|
<int:service-activator input-channel="in" output-channel="out"
|
|
expression="@accountService.processAccount(payload, headers.accountId)"/>
|
|
|
|
<bean id="accountService" class="thing1.thing2.Account"/>
|
|
----
|
|
====
|
|
|
|
In the preceding configuration, instead of injecting 'accountService' by using a `ref` or as an inner bean, we use SpEL's `@beanId` notation and invoke a method that takes a type compatible with the message payload.
|
|
We also pass a header value.
|
|
Any valid SpEL expression can be evaluated against any content in the message.
|
|
For simple scenarios, your service activators need not reference a bean if all logic can be encapsulated in such an expression, as the following example shows:
|
|
|
|
====
|
|
[source,xml]
|
|
----
|
|
<int:service-activator input-channel="in" output-channel="out" expression="payload * 2"/>
|
|
----
|
|
====
|
|
|
|
In the preceding configuration, our service logic is to multiply the payload value by two.
|
|
SpEL lets us handle it relatively easily.
|
|
|
|
See <<./dsl.adoc#java-dsl-handle,Service Activators and the `.handle()` method>> in the Java DSL chapter for more information about configuring service activator.
|
|
|
|
[[async-service-activator]]
|
|
==== Asynchronous Service Activator
|
|
|
|
The service activator is invoked by the calling thread.
|
|
This is an upstream thread if the input channel is a `SubscribableChannel` or a poller thread for a `PollableChannel`.
|
|
If the service returns a `CompletableFuture<?>`, the default action is to send that as the payload of the message sent to the output (or reply) channel.
|
|
Starting with version 4.3, you can now set the `async` attribute to `true` (by using `setAsync(true)` when using Java configuration).
|
|
If the service returns a `CompletableFuture<?>` when this the `async` attribute is set to `true`, the calling thread is released immediately and the reply message is sent on the thread (from within your service) that completes the future.
|
|
This is particularly advantageous for long-running services that use a `PollableChannel`, because the poller thread is released to perform other services within the framework.
|
|
|
|
If the service completes the future with an `Exception`, normal error processing occurs.
|
|
An `ErrorMessage` is sent to the `errorChannel` message header, if present.
|
|
Otherwise, an `ErrorMessage` is sent to the default `errorChannel` (if available).
|
|
|
|
Starting with version 6.1, if the output channel of the `AbstractMessageProducingHandler` is configured to a `ReactiveStreamsSubscribableChannel`, the async mode is turned on by default.
|
|
If the handler result is not a reactive type or `CompletableFuture<?>`, then regular reply producing process happens despite the output channel type.
|
|
|
|
See also <<./reactive-streams.adoc#reactive-streams,Reactive Streams Support>> for more information.
|
|
|
|
[[service-activator-return-type]]
|
|
==== Service Activator and Method Return Type
|
|
|
|
The service method can return any type which becomes reply message payload.
|
|
In this case a new `Message<?>` object is created and all the headers from a request message are copied.
|
|
This works the same way for most Spring Integration `MessageHandler` implementations, when interaction is based on a POJO method invocation.
|
|
|
|
A complete `Message<?>` object can also be returned from the method.
|
|
However, keep in mind that, unlike <<./transformer.adoc#transformer, transformers>>, for a Service Activator this message will be modified by copying the headers from the request message if they are not already present in the returned message.
|
|
So, if your method parameter is a `Message<?>` and you copy some, but not all, existing headers in your service method, they will reappear in the reply message.
|
|
It is not a Service Activator responsibility to remove headers from a reply message and, pursuing the loosely-coupled principle, it is better to add a `HeaderFilter` in the integration flow.
|
|
Alternatively, a Transformer can be used instead of a Service Activator but, in that case, when returning a full `Message<?>` the method is completely responsible for the message, including copying request message headers (if needed).
|
|
You must ensure that important framework headers (e.g. `replyChannel`, `errorChannel`), if present, have to be preserved.
|