Add Reactive Stream chapter into docs

* Improve `channel.adoc`: polishing, new channels, java config
* Make `LoadBalancingStrategy` in the `DirectChannel` ctor as
`@Nullable` to reflect the real logic behind

* More Dos and polishing according PR comments
* Mention WebFlux and RSocket endpoints in the `endpoint-summary.adoc`
table

* More reactive streams docs
* `SourcePollingChannelAdapter` polishing
* Wrap multi-value publisher into `Mono.just()` in the `AbstractMessageProducingHandler`
instead of emitting just only a first item for the standard reply

Doc polishing
This commit is contained in:
Artem Bilan
2019-09-27 11:45:34 -04:00
committed by Gary Russell
parent eeb951b0da
commit ad96ca49f1
11 changed files with 369 additions and 23 deletions

View File

@@ -20,6 +20,7 @@ import org.springframework.integration.context.IntegrationProperties;
import org.springframework.integration.dispatcher.LoadBalancingStrategy;
import org.springframework.integration.dispatcher.RoundRobinLoadBalancingStrategy;
import org.springframework.integration.dispatcher.UnicastingDispatcher;
import org.springframework.lang.Nullable;
/**
* A channel that invokes a single subscriber for each sent Message.
@@ -30,6 +31,7 @@ import org.springframework.integration.dispatcher.UnicastingDispatcher;
* @author Iwein Fuld
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class DirectChannel extends AbstractSubscribableChannel {
@@ -45,12 +47,13 @@ public class DirectChannel extends AbstractSubscribableChannel {
}
/**
* Create a DirectChannel with a {@link LoadBalancingStrategy}. The
* strategy <em>must not</em> be null.
*
* Create a DirectChannel with a {@link LoadBalancingStrategy}.
* Can be {@code null} meaning that no balancing is applied;
* every message is always going to be handled by the first subscriber.
* @param loadBalancingStrategy The load balancing strategy implementation.
* @see #setFailover(boolean)
*/
public DirectChannel(LoadBalancingStrategy loadBalancingStrategy) {
public DirectChannel(@Nullable LoadBalancingStrategy loadBalancingStrategy) {
this.dispatcher.setLoadBalancingStrategy(loadBalancingStrategy);
}
@@ -58,7 +61,6 @@ public class DirectChannel extends AbstractSubscribableChannel {
/**
* Specify whether the channel's dispatcher should have failover enabled.
* By default, it will. Set this value to 'false' to disable it.
*
* @param failover The failover boolean.
*/
public void setFailover(boolean failover) {
@@ -68,7 +70,6 @@ public class DirectChannel extends AbstractSubscribableChannel {
/**
* Specify the maximum number of subscribers supported by the
* channel's dispatcher.
*
* @param maxSubscribers The maximum number of subscribers allowed.
*/
public void setMaxSubscribers(int maxSubscribers) {
@@ -85,9 +86,8 @@ public class DirectChannel extends AbstractSubscribableChannel {
protected void onInit() {
super.onInit();
if (this.maxSubscribers == null) {
Integer max = this.getIntegrationProperty(IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS,
Integer.class);
this.setMaxSubscribers(max);
Integer max = getIntegrationProperty(IntegrationProperties.CHANNELS_MAX_UNICAST_SUBSCRIBERS, Integer.class);
setMaxSubscribers(max);
}
}

View File

@@ -63,7 +63,7 @@ public class PollingConsumer extends AbstractPollingEndpoint implements Integrat
public PollingConsumer(PollableChannel inputChannel, MessageHandler handler) {
Assert.notNull(inputChannel, "inputChannel must not be null");
Assert.notNull(handler, "handler must not be null");
if (inputChannel instanceof NullChannel && logger.isWarnEnabled()) {
if (inputChannel instanceof NullChannel) {
logger.warn("The polling from the NullChannel does not have any effects: " +
"it doesn't forward messages sent to it. A NullChannel is the end of the flow.");
}
@@ -134,7 +134,7 @@ public class PollingConsumer extends AbstractPollingEndpoint implements Integrat
try {
if (this.channelInterceptors != null
&& ((ExecutorChannelInterceptorAware) this.inputChannel).hasExecutorInterceptors()) {
interceptorStack = new ArrayDeque<ExecutorChannelInterceptor>();
interceptorStack = new ArrayDeque<>();
theMessage = applyBeforeHandle(theMessage, interceptorStack);
if (theMessage == null) {
return;

View File

@@ -147,11 +147,11 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint
if (AopUtils.isAopProxy(this.source)) {
Advised advised = (Advised) this.source;
this.appliedAdvices.forEach(advised::removeAdvice);
chain.stream().forEach(advice -> advised.addAdvisor(adviceToReceiveAdvisor(advice)));
chain.forEach(advice -> advised.addAdvisor(adviceToReceiveAdvisor(advice)));
}
else {
ProxyFactory proxyFactory = new ProxyFactory(this.source);
chain.stream().forEach(advice -> proxyFactory.addAdvisor(adviceToReceiveAdvisor(advice)));
chain.forEach(advice -> proxyFactory.addAdvisor(adviceToReceiveAdvisor(advice)));
this.source = (MessageSource<?>) proxyFactory.getProxy(getBeanClassLoader());
}
this.appliedAdvices.clear();

View File

@@ -27,6 +27,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.reactivestreams.Publisher;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.ReactiveStreamsSubscribableChannel;
import org.springframework.integration.context.IntegrationContextUtils;
@@ -346,8 +348,15 @@ public abstract class AbstractMessageProducingHandler extends AbstractMessageHan
}
else {
SettableListenableFuture<Object> settableListenableFuture = new SettableListenableFuture<>();
Mono.from((Publisher<?>) reply)
.subscribe(settableListenableFuture::set, settableListenableFuture::setException);
Mono<?> reactiveReply;
ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(null, reply);
if (adapter != null && adapter.isMultiValue()) {
reactiveReply = Mono.just(reply);
}
else {
reactiveReply = Mono.from((Publisher<?>) reply);
}
reactiveReply.subscribe(settableListenableFuture::set, settableListenableFuture::setException);
future = settableListenableFuture;
}
future.addCallback(new ReplyFutureCallback(requestMessage, replyChannel));

View File

@@ -146,8 +146,7 @@ In addition to being the simplest point-to-point channel option, one of its most
For example, if a handler subscribes to a `DirectChannel`, then sending a `Message` to that channel triggers invocation of that handler's `handleMessage(Message)` method directly in the sender's thread, before the `send()` method invocation can return.
The key motivation for providing a channel implementation with this behavior is to support transactions that must span across the channel while still benefiting from the abstraction and loose coupling that the channel provides.
If the send call is invoked within the scope of a transaction, the outcome of the handler's invocation (for example,
updating a database record) plays a role in determining the ultimate result of that transaction (commit or rollback).
If the send call is invoked within the scope of a transaction, the outcome of the handler's invocation (for example, updating a database record) plays a role in determining the ultimate result of that transaction (commit or rollback).
NOTE: Since the `DirectChannel` is the simplest option and does not add any additional overhead that would be required for scheduling and managing the threads of a poller, it is the default channel type within Spring Integration.
The general idea is to define the channels for an application, consider which of those need to provide buffering or to throttle input, and modify those to be queue-based `PollableChannels`.
@@ -161,6 +160,9 @@ As a convenience, the `load-balancer` attribute exposes an enumeration of values
Other strategy implementations may be added in future versions.
However, since version 3.0, you can provide your own implementation of the `LoadBalancingStrategy` and inject it by using the `load-balancer-ref` attribute, which should point to a bean that implements `LoadBalancingStrategy`, as the following example shows:
A `FixedSubscriberChannel` is a `SubscribableChannel` that only supports a single `MessageHandler` subscriber that cannot be unsubscribed.
This is useful for high-throughput performance use-cases when no other subscribers are involved and no channel interceptors are needed.
====
[source,xml]
----
@@ -201,6 +203,16 @@ CAUTION: The sender can sometimes block.
For example, when using a `TaskExecutor` with a rejection policy that throttles the client (such as the `ThreadPoolExecutor.CallerRunsPolicy`), the sender's thread can execute the method any time the thread pool is at its maximum capacity and the executor's work queue is full.
Since that situation would only occur in a non-predictable way, you should not rely upon it for transactions.
[[flux-message-channel]]
===== `FluxMessageChannel`
The `FluxMessageChannel` is an `org.reactivestreams.Publisher` implementation for `"sinking"` sent messages into an internal `reactor.core.publisher.Flux` for on demand consumption by reactive subscribers downstream.
This channel implementation is neither a `SubscribableChannel`, nor a `PollableChannel`, so only `org.reactivestreams.Subscriber` instances can be used to consume from this channel honoring back-pressure nature of reactive streams.
On the other hand, the `FluxMessageChannel` implements a `ReactiveStreamsSubscribableChannel` with its `subscribeTo(Publisher<Message<?>>)` contract allowing receiving events from reactive source publishers, bridging a reactive stream into the integration flow.
To achieve fully reactive behavior for the whole integration flow, such a channel must be placed between all the endpoints in the flow.
See <<./reactive-streams.adoc#reactive-streams,Reactive Streams Support>> for more information about interaction with Reactive Streams.
[[channel-implementations-threadlocalchannel]]
===== Scoped Channel
@@ -348,7 +360,7 @@ NOTE: A less invasive approach that lets you invoke simple interfaces with paylo
[[channel-configuration]]
==== Configuring Message Channels
To create a message channel instance, you can use the <channel/> element, as follows:
To create a message channel instance, you can use the `<channel/>` element, as follows:
====
[source,xml]
@@ -357,6 +369,19 @@ To create a message channel instance, you can use the <channel/> element, as fol
----
====
The equivalent Java configuration declares a `DirectChannel` `@Bean`:
====
[source,java]
----
@Bean
public MessageChannel exampleChannel() {
return new DirectChannel();
}
----
====
The default channel type is point-to-point.
To create a publish-subscribe channel, use the `<publish-subscribe-channel/>` element, as follows:
@@ -367,6 +392,18 @@ To create a publish-subscribe channel, use the `<publish-subscribe-channel/>` el
----
====
The Java configuration is:
====
[source,java]
----
@Bean
public MessageChannel exampleChannel() {
return new PublishSubscribeChannel();
}
----
====
When you use the `<channel/>` element without any sub-elements, it creates a `DirectChannel` instance (a `SubscribableChannel`).
However, you can alternatively provide a variety of `<queue/>` sub-elements to create any of the pollable channel types (as described in <<channel-implementations>>).
@@ -376,7 +413,7 @@ The following sections shows examples of each channel type.
===== `DirectChannel` Configuration
As mentioned earlier, `DirectChannel` is the default type.
The following listing shows who to define one in XML:
The following listing shows who to define one:
====
[source,xml]
@@ -385,6 +422,18 @@ The following listing shows who to define one in XML:
----
====
In Java Configuration:
====
[source,java]
----
@Bean
public MessageChannel directChannel() {
return new DirectChannel();
}
----
====
A default channel has a round-robin load-balancer and also has failover enabled (see <<channel-implementations-directchannel>> for more detail).
To disable one or both of these, add a `<dispatcher/>` sub-element and configure the attributes as follows:
@@ -401,6 +450,25 @@ To disable one or both of these, add a `<dispatcher/>` sub-element and configure
----
====
In Java Configuration:
====
[source,java]
----
@Bean
public MessageChannel failFastChannel() {
DirectChannel channel = new DirectChannel();
channel.setFailover(false);
return channel;
}
@Bean
public MessageChannel failFastChannel() {
return new DirectChannel(null);
}
----
====
[[channel-datatype-channel]]
===== Datatype Channel Configuration
@@ -412,10 +480,27 @@ This would work, but a simpler way to accomplish the same thing is to apply the
You can use separate datatype channels for each specific payload data type.
To create a datatype channel that accepts only messages that contain a certain payload type, provide the data type's fully-qualified class name in the channel element's `datatype` attribute, as the following example shows:
====
[source,xml]
----
<int:channel id="numberChannel" datatype="java.lang.Number"/>
----
====
In Java Configuration:
====
[source,java]
----
@Bean
public MessageChannel numberChannel() {
DirectChannel channel = new DirectChannel();
channel.setDatatypes(Number.class);
return channel;
}
----
====
Note that the type check passes for any type that is assignable to the channel's datatype.
In other words, the `numberChannel` in the preceding example would accept messages whose payload is `java.lang.Integer` or `java.lang.Double`.
@@ -429,7 +514,8 @@ Multiple types can be provided as a comma-delimited list, as the following examp
====
So the 'numberChannel' in the preceding example accepts only messages with a data type of `java.lang.Number`.
But what happens if the payload of the message is not of the required type? It depends on whether you have defined a bean named `integrationConversionService` that is an instance of Spring's https://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert-ConversionService-API[Conversion Service].
But what happens if the payload of the message is not of the required type?
It depends on whether you have defined a bean named `integrationConversionService` that is an instance of Spring's https://docs.spring.io/spring/docs/current/spring-framework-reference/html/validation.html#core-convert-ConversionService-API[Conversion Service].
If not, then an `Exception` would be thrown immediately.
However, if you have defined an `integrationConversionService` bean, it is used in an attempt to convert the message's payload to the acceptable type.
@@ -437,11 +523,13 @@ You can even register custom converters.
For example, suppose you send a message with a `String` payload to the 'numberChannel' we configured above.
You might handle the message as follows:
====
[source,java]
----
MessageChannel inChannel = context.getBean("numberChannel", MessageChannel.class);
inChannel.send(new GenericMessage<String>("5"));
----
====
Typically this would be a perfectly legal operation.
However, since we use Datatype Channel, the result of such operation would generate an exception similar to the following:
@@ -473,12 +561,30 @@ public static class StringToIntegerConverter implements Converter<String, Intege
====
Then we can register it as a converter with the Integration Conversion Service, as the following example shows:
[source,java]
====
[source,xml]
----
<int:converter ref="strToInt"/>
<bean id="strToInt" class="org.springframework.integration.util.Demo.StringToIntegerConverter"/>
----
====
With Java Configuration you must use an `@IntegrationConverter` next to a `@Bean` annotation:
====
[source,java]
----
@Bean
@IntegrationConverter
public StringToIntegerConverter strToInt {
return new StringToIntegerConverter();
}
----
====
Or on the `StringToIntegerConverter` class when it is marked with the `@Component` annotation for auto-scanning.
When the 'converter' element is parsed, it creates the `integrationConversionService` bean if one is not already defined.
With that converter in place, the `send` operation would now be successful, because the datatype channel uses that converter to convert the `String` payload to an `Integer`.
@@ -510,9 +616,22 @@ You may specify the channel's capacity as follows:
----
====
NOTE: If you do not provide a value for the 'capacity' attribute on this `<queue/>` sub-element, the resulting queue is unbounded.
To avoid issues such as running out of memory, we highly recommend that you set an explicit value for a bounded queue.
With Java Configuration:
====
[source,java]
----
@Bean
public PollableChannel queueChannel() {
return new QueueChannel(25);
}
----
====
====== Persistent `QueueChannel` Configuration
Since a `QueueChannel` provides the capability to buffer messages but does so in-memory only by default, it also introduces a possibility that messages could be lost in the event of a system failure.
@@ -543,6 +662,8 @@ You can configure a message store for any `QueueChannel` by adding the `message-
----
====
(See samples below for Java Configuration options.)
The Spring Integration JDBC module also provides a schema Data Definition Language (DDL) for a number of popular databases.
These schemas are located in the org.springframework.integration.jdbc.store.channel package of that module (`spring-integration-jdbc`).
@@ -625,6 +746,18 @@ When using this element, you can also specify the `task-executor` used for publi
----
====
With Java Configuration:
====
[source,java]
----
@Bean
public MessageChannel pubsubChannel() {
return new PublishSubscribeChannel(someExecutor());
}
----
====
If you provide a resequencer or aggregator downstream from a `PublishSubscribeChannel`, you can set the 'apply-sequence' property on the channel to `true`.
Doing so indicates that the channel should set the `sequence-size` and `sequence-number` message headers as well as the correlation ID prior to passing along the messages.
For example, if there are five subscribers, the `sequence-size` would be set to `5`, and the messages would have `sequence-number` header values ranging from `1` to `5`.
@@ -646,6 +779,18 @@ The following example shows how to set the `apply-sequence` header to `true`:
----
====
====
[source,java]
----
@Bean
public MessageChannel pubsubChannel() {
PublishSubscribeChannel channel = new PublishSubscribeChannel();
channel.setApplySequence(false);
return channel;
}
----
====
NOTE: The `apply-sequence` value is `false` by default so that a publish-subscribe channel can send the exact same message instances to multiple outbound channels.
Since Spring Integration enforces immutability of the payload and header references, when the flag is set to `true`, the channel creates new `Message` instances with the same payload reference but different header values.
@@ -667,6 +812,18 @@ The following example shows how to use the `dispatcher` element and specify an e
----
====
In Java Configuration you must use an `ExecutorChannel` bean definition:
====
[source,java]
----
@Bean
public MessageChannel executorChannel() {
return new ExecutorChannel(someExecutor());
}
----
====
[NOTE]
=====
The `load-balancer` and `failover` options are also both available on the <dispatcher/> sub-element, as described earlier in <<channel-configuration-directchannel>>.
@@ -697,6 +854,18 @@ To create a `PriorityChannel`, use the `<priority-queue/>` sub-element, as the f
----
====
In JavaConfiguration:
====
[source,java]
----
@Bean
public PollableChannel priorityChannel() {
return new PriorityChannel(20);
}
----
====
By default, the channel consults the `priority` header of the message.
However, you can instead provide a custom `Comparator` reference.
Also, note that the `PriorityChannel` (like the other types) does support the `datatype` attribute.
@@ -713,6 +882,18 @@ The following example demonstrates all of these:
----
====
====
[source,java]
----
@Bean
public PollableChannel priorityChannel() {
PriorityChannel channel = new PriorityChannel(20, widgetComparator());
channel.setDatatypes(example.Widget.class);
return channel;
}
----
====
Since version 4.0, the `priority-channel` child element supports the `message-store` option (`comparator` and `capacity` are not allowed in that case).
The message store must be a `PriorityCapableChannelMessageStore`.
Implementations of the `PriorityCapableChannelMessageStore` are currently provided for `Redis`, `JDBC`, and `MongoDB`.
@@ -726,13 +907,24 @@ A `RendezvousChannel` is created when the queue sub-element is a `<rendezvous-qu
It does not provide any additional configuration options to those described earlier, and its queue does not accept any capacity value, since it is a zero-capacity direct handoff queue.
The following example shows how to declare a `RendezvousChannel`:
====
[source,xml]
----
<int:channel id="rendezvousChannel"/>
<int:rendezvous-queue/>
</int:channel>
----
====
====
[source,java]
----
@Bean
public PollableChannel rendezvousChannel() {
return new RendezvousChannel();
}
----
====
[[channel-configuration-threadlocalchannel]]
===== Scoped Channel Configuration
@@ -934,11 +1126,14 @@ Similarly, the `selector-expression` is a boolean SpEL expression that performs
It is possible to configure a global wire tap as a special case of the <<global-channel-configuration-interceptors>>.
To do so, configure a top level `wire-tap` element.
Now, in addition to the normal `wire-tap` namespace support, the `pattern` and `order` attributes are supported and work in exactly the same way as they do for the `channel-interceptor`.
The following examlpe shows how to configure a global wire tap:
The following example shows how to configure a global wire tap:
====
[source,xml]
----
<int:wire-tap pattern="input*, thing2*, thing1" order="3" channel="wiretapChannel"/>
----
====
TIP: A global wire tap provides a convenient way to configure a single-channel wire tap externally without modifying the existing channel configuration.
To do so, set the `pattern` attribute to the target channel name.

View File

@@ -156,6 +156,11 @@ The following table summarizes the various endpoints with quick links to the app
| <<./rmi.adoc#rmi-inbound,Inbound RMI>>
| <<./rmi.adoc#rmi-outbound,Outbound RMI>>
| *RSocket*
| N
| N
| <<./rsocket.adoc#rsocket-inbound,RSocket Inbound Gateway>>
| <<./rsocket.adoc#rsocket-outbound,RSocket Outbound Gateway>>
| *SFTP*
| <<./sftp.adoc#sftp-inbound,SFTP Inbound Channel Adapter>>
@@ -193,6 +198,12 @@ The following table summarizes the various endpoints with quick links to the app
| N
| N
| *WebFlux*
| <<./webflux.adoc#webflux-inbound,WebFlux Inbound Channel Adapter>>
| <<./webflux.adoc#webflux-outbound,WebFlux Outbound Channel Adapter>>
| <<./webflux.adoc#webflux-inbound,Inbound WebFlux Gateway>>
| <<./webflux.adoc#webflux-outbound,Outbound WebFlux Gateway>>
| *Web Services*
| N
| N

View File

@@ -708,6 +708,7 @@ String out = result.get(10, TimeUnit.SECONDS);
----
====
[[reactor-mono]]
===== Reactor `Mono`
Starting with version 5.0, the `GatewayProxyFactoryBean` allows the use of https://projectreactor.io/[Project Reactor] with gateway interface methods, using a https://github.com/reactor/reactor-core[`Mono<T>`] return type.

View File

@@ -16,6 +16,7 @@ as single searchable link:index-single.html[html] and link:../pdf/spring-integra
<<./messaging-endpoints.adoc#messaging-endpoints-chapter,Messaging Endpoints>> ::
<<./dsl.adoc#java-dsl,Java DSL>> ::
<<./system-management.adoc#system-management-chapter,System Management>> ::
<<./reactive-streams.adoc#reactive-streams,Reactive Streams Support>> ::
[horizontal]
**Integration Endpoints** ::

View File

@@ -0,0 +1,126 @@
[[reactive-streams]]
== Reactive Streams Support
Spring Integration provides support for https://www.reactive-streams.org/[Reactive Streams] interaction in some places of the framework and from different aspects.
We will discuss most of them here with appropriate links to the target chapters for details whenever necessary.
=== Preface
To recap, Spring Integration extends the Spring programming model to support the well-known Enterprise Integration Patterns.
Spring Integration enables lightweight messaging within Spring-based applications and supports integration with external systems via declarative adapters.
Spring Integrations primary goal is to provide a simple model for building enterprise integration solutions while maintaining the separation of concerns that is essential for producing maintainable, testable code.
This goal is achieved in the target application using first class citizens like `message`, `channel` and `endpoint`, which allow us to build an integration flow (pipeline), where (in most cases) one endpoint produces messages into a channel to be consumed by another endpoint.
This way we distinguish an integration interaction model from the target business logic.
The crucial part here is a channel in between: the flow behavior depends from its implementation leaving endpoints untouched.
On the other hand, the Reactive Streams is a standard for asynchronous stream processing with non-blocking back pressure.
The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary like passing elements on to another thread or thread-pool while ensuring that the receiving side is not forced to buffer arbitrary amounts of data.
In other words, back pressure is an integral part of this model in order to allow the queues which mediate between threads to be bounded.
The intention of Reactive Streams implementation, such as https://projectreactor.io/[Project Reactor], is to preserve these benefits and characteristics across the whole processing graph of a stream application.
The ultimate goal of Reactive Streams libraries is to provide types, set of operators and supporting API for a target application in a transparent and smooth manner as is possible with available programming language structure, but the final solution is not as imperative as it is with a normal function chain invocation.
It is divided into to phases: definition and execution, which happens some time later during subscription to the final reactive publisher, and demand for data is pushed from the bottom of the definition to the top applying back-pressure as needed - we request as many events as we can handle at the moment.
The reactive application looks like a `"stream"` or as we got used to in Spring Integration terms - `"flow"`.
In fact the Reactive Streams SPI since Java 9 is presented in the `java.util.concurrent.Flow` class.
From here it may look like Spring Integration flows are really a good fit for writing Reactive Streams applications when we apply some reactive framework operators on endpoints, but in fact the problems is much broader and we need to keep in mind that not all endpoints (e.g. `JdbcMessageHandler`) can be processed in a reactive stream transparently.
Of course, the main goal for Reactive Streams support in Spring Integration is to allow the whole process to be fully reactive, on demand initiated and back-pressure ready.
It is not going to be possible until the target protocols and systems for channel adapters provide a Reactive Streams interaction model.
In the sections below we will describe what components and approaches are provided in Spring Integration for developing reactive application preserving integration flow structures.
NOTE: All the Reactive Streams interaction in Spring Integration implemented with https://projectreactor.io/[Project Reactor] types, such as `Mono` and `Flux`.
=== Messaging Gateway
The simplest point of interaction with Reactive Streams is a `@MessagingGateway` where we just make a return type of the gateway method as a `Mono<?>` - and the whole integration flow behind a gateway method call is going to be performed when a subscription happens on the returned `Mono` instance.
See <./gateway.adoc#reactor-mono,Reactor `Mono`>> for more information.
A similar `Mono`-reply approach is used in the framework internally for inbound gateways which are fully based on Reactive Streams compatible protocols (see <<reactive-channel-adapters>> below for more information).
The send-and-receive operation is wrapped into a `Mono.deffer()` with chaining a reply evaluation from the `replyChannel` header whenever it is available.
This way an inbound component for the particular reactive protocol (e.g. Netty) is going to be as a subscriber and initiator for a reactive flow performed on the Spring Integration.
If the request payload is a reactive type, it would be better to handle it withing a reactive stream definition deferring a process to the initiator subscription.
For this purpose a handler method must return a reactive type as well.
See the next section for more information.
=== Reactive Reply Payload
When a reply producing `MessageHandler` returns a reactive type payload for a reply message, it is processed in an asynchronous manner with a regular `MessageChannel` implementation provided for the `outputChannel` and flattened with on demand subscription when the output channel is a `ReactiveStreamsSubscribableChannel` implementation, e.g. `FluxMessageChannel`.
With a standard imperative `MessageChannel` use-case, and if a reply payload is a *multi-value* publisher (see `ReactiveAdapter.isMultiValue()` for more information), it is wrapped into a `Mono.just()`.
A result of this, the `Mono` has to be subscribed explicitly downstream or flattened by the `FluxMessageChannel` downstream.
With a `ReactiveStreamsSubscribableChannel` for the `outputChannel`, there is no need to be concerned about return type and subscription; everything is processed smoothly by the framework internally.
See <./service-activator.adoc#async-service-activator,Asynchronous Service Activator>> for more information.
=== `FluxMessageChannel` and `ReactiveStreamsConsumer`
The `FluxMessageChannel` is a combined implementation of `MessageChannel` and `Publisher<Message<?>>`.
A `Flux`, as a hot source, is created internally for sinking incoming messages from the `send()` implementation.
The `Publisher.subscribe()` implementation is delegated to that internal `Flux`.
Also, for on demand upstream consumption, the `FluxMessageChannel` provides an implementation for the `ReactiveStreamsSubscribableChannel` contract.
Any upstream `Publisher` (see Source Polling Channel Adapter and splitter below, for example) provided for this channel is auto-subscribed when subscription is ready for this channel.
Events from this delegating publishers are sunk into an internal `Flux` mentioned above.
A consumer for the `FluxMessageChannel` must be a `org.reactivestreams.Subscriber` instance for honoring the Reactive Streams contract.
Fortunately, all of the `MessageHandler` implementations in Spring Integration also implement a `CoreSubscriber` from project Reactor.
And thanks to a `ReactiveStreamsConsumer` implementation in between, the whole integration flow configuration is left transparent for target developers.
In this case, the flow behavior is changed from an imperative push model to a reactive pull model.
A `ReactiveStreamsConsumer` can also be used to turn any `MessageChannel` into a reactive source using `MessageChannelReactiveUtils`, making an integration flow partially reactive.
See <./channel.adoc#flux-message-channel,`FluxMessageChannel`>> for more information.
=== Source Polling Channel Adapter
Usually, the `SourcePollingChannelAdapter` relies on the task which is initiated by the `TaskScheduler`.
A polling trigger is built from the provided options and used for periodic scheduling a task to poll a target source of data or events.
When an `outputChannel` is a `ReactiveStreamsSubscribableChannel`, the same `Trigger` is used to determine the next time for execution, but instead of scheduling tasks, the `SourcePollingChannelAdapter` creates a `Flux<Message<?>>` based on the `Flux.generate()` for the `nextExecutionTime` values and `Mono.delay()` for a duration from the previous step.
A `Flux.flatMapMany()` is used then to poll `maxMessagesPerPoll` and sink them into an output `Flux`.
This generator `Flux` is subscribed by the provided `ReactiveStreamsSubscribableChannel` honoring a back-pressure downstream.
This way, any `MessageSource` implementation can be turned into a reactive hot source.
See <./polling-consumer.adoc#polling-consumer,Polling Consumer>> for more information.
=== Splitter and Aggregator
When an `AbstractMessageSplitter` gets a `Publisher` for its logic, the process goes naturally over the items in the `Publisher` to map them into messages for sending to the `outputChannel`.
If this channel is a `ReactiveStreamsSubscribableChannel`, the `Flux` wrapper for the `Publisher` is subscribed on demand from that channel and this splitter behavior looks more like a `flatMap` Reactor operator, when we map an incoming event into multi-value output `Publisher`.
It makes most sense when the whole integration flow is built with a `FluxMessageChannel` before and after the splitter, aligning Spring Integration configuration with a Reactive Streams requirements and its operators for event processing.
With a regular channel, a `Publisher` is converted into an `Iterable` for standard iterate-and-produce splitting logic.
A `FluxAggregatorMessageHandler` is another sample of specific Reactive Streams logic implementation which could be treated as a `"reactive operator"` in terms of Project Reactor.
It is based on the `Flux.groupBy()` and `Flux.window()` (or `buffer()`) operators.
The incoming messages are sunk into a `Flux.create()` initiated when a `FluxAggregatorMessageHandler` is created, making it as a hot source.
This `Flux` is subscribed to by a `ReactiveStreamsSubscribableChannel` on demand, or directly in the `FluxAggregatorMessageHandler.start()` when the `outputChannel` is not reactive.
This `MessageHandler` has its power, when the whole integration flow is built with a `FluxMessageChannel` before and after this component, making the whole logic back-pressure ready.
See <./splitter.adoc#split-stream-and-flux,Stream and Flux Splitting>> and <./aggregator.adoc#flux-aggregator,Flux Aggregator>> for more information.
=== Java DSL
An `IntegrationFlow` in Java DSL can start from any `Publisher` instance (see `IntegrationFlows.from(Publisher<Message<T>>)`).
Also, with an `IntegrationFlowBuilder.toReactivePublisher()` operator, the `IntegrationFlow` can be turned into a reactive hot source.
A `FluxMessageChannel` is used internally in both cases; it can subscribe to an inbound `Publisher` according to its `ReactiveStreamsSubscribableChannel` contract and it is a `Publisher<Message<?>>` by itself for downstream subscribers.
With a dynamic `IntegrationFlow` registration we can implement a powerful logic combining Reactive Streams with this integration flow bringing to/from `Publisher`.
For the exact opposite use-case, when `IntegrationFlow` should call a reactive stream and continue after completion, a `fluxTransform()` operator is provided in the `IntegrationFlowDefinition`.
The flow at this point is turned into a `FluxMessageChannel` which is propagated into a provided `fluxFunction`, performed in the `Flux.transform()` operator.
A result of the function is wrapped into a `Mono<Message<?>>` for flat-mapping into an output `Flux` which is subscribed by another `FluxMessageChannel` for downstream flow.
See <./dsl.adoc#java-dsl,Java DSL Chapter>> for more information.
[[reactive-channel-adapters]]
=== Reactive Channel Adapters
When the target protocol for integration provides a Reactive Streams solution, it becomes straightforward to implement channel adapters in Spring Integration.
An inbound, event-driven channel adapter implementation is about wrapping a request (if necessary) into a deferred `Mono` or `Flux` and perform a send (and produce reply, if any) only when a protocol component initiates a subscription into a `Mono` returned from the listener method.
This way we have a reactive stream solution encapsulated exactly in this component.
Of course, downstream integration flow subscribed on the output channel should honor Reactive Streams specification and be performed in the on demand, back-pressure ready manner.
This is not always available by the nature (or the current implementation) of `MessageHandler` processor used in the integration flow.
This limitation can be handled using thread pools and queues or `FluxMessageChannel` (see above) before and after integration endpoints when there is no reactive implementation.
A reactive outbound channel adapter implementation is about initiation (or continuation) of a reactive stream to interaction with an external system according provided reactive API for the target protocol.
An inbound payload could be a reactive type per se or as an event of the whole integration flow which is a part of reactive stream on top.
A returned reactive type can be subscribed immediately if we are in one-way, fire-and-forget scenario, or it is propagated downstream (request-reply scenarios) for further integration flow or an explicit subscription in the target business logic, but still downstream preserving reactive streams semantics.
Currently Spring Integration provides channel adapter (or gateway) implementations for <./webflux.adoc#webflux,WebFlux>> and <./rsocket.adoc#rsocket,RSocket>>.
Also an https://github.com/spring-projects/spring-integration-extensions/tree/master/spring-integration-cassandra[Apache Cassandra Extension] provides a `MessageHandler` implementation for the Cassandra reactive driver.
More reactive channel adapters are coming, for example for https://r2dbc.io/[R2DBC], https://mongodb.github.io/mongo-java-driver-reactivestreams/[MongoDB], for Apache Kafka in https://github.com/spring-projects/spring-integration-kafka[Spring Integration Kafka] based on the `ReactiveKafkaProducerTemplate` and `ReactiveKafkaConsumerTemplate` from https://spring.io/projects/spring-kafka[Spring for Apache Kafka] etc.
For many other non-reactive channel adapters thread pools are recommended to avoid blocking during reactive stream processing.

View File

@@ -32,10 +32,12 @@ This can be useful when using <<./endpoint.adoc#content-type-conversion, content
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:

View File

@@ -57,6 +57,7 @@ And starting with version 5.0.9, this method also properly returns a size of the
An `Iterator` object is useful to avoid the need for building an entire collection in the memory before splitting.
For example, when underlying items are populated from some external system (e.g. DataBase or FTP `MGET`) using iterations or streams.
[[split-stream-and-flux]]
===== Stream and Flux
Starting with version 5.0, the `AbstractMessageSplitter` supports the Java `Stream` and Reactive Streams `Publisher` types for the `value` to split.