GH-3902: Add Kotlin Coroutines Support (#3905)
* GH-3902: Add Kotlin Coroutines Support Fixes https://github.com/spring-projects/spring-integration/issues/3902 * Add `isAsync()` propagation from the `MessagingMethodInvokerHelper` to the `AbstractMessageProducingHandler` to set into its `async` property. The logic is based on a `CompletableFuture`, `Publisher` or Kotlin `suspend` return types of the POJO method * Introduce `IntegrationMessageHandlerMethodFactory` and `IntegrationInvocableHandlerMethod` to extend the logic to newly introduced `ContinuationHandlerMethodArgumentResolver` and call for Kotlin suspend functions. * Remove `MessageHandlerMethodFactoryCreatingFactoryBean` since its logic now is covered with the `IntegrationMessageHandlerMethodFactory` * Kotlin suspend functions are essentially reactive, so use `CoroutinesUtils.invokeSuspendingFunction()` and existing logic in the `AbstractMessageProducingHandler` to deal with `Publisher` reply * Fix `GroovySplitterTests` for the current code base * Add `kotlinx.coroutines.flow.Flow` support The `Flow` is essentially a multi-value reactive `Publisher`, so use `ReactiveAdapterRegistry` to convert any custom reactive streams result to `Flux` and `Mono` which we already support as reply types * Add docs for `Kotlin Coroutines` Rearrange the doc a bit extracting Kotlin support to individual `kotlin-functions.adoc` file * Fix missed link to `reactive-streams.adoc` from the `index-single.adoc` * Fix unintended Javadocs formatting in the `AbstractMessageProducingHandler` * Add suspend functions support for Messaging Gateway * Add convenient `CoroutinesUtils` for Coroutines types and `Continuation` argument fulfilling via `Mono` * Treat `suspend fun` in the `GatewayProxyFactoryBean` as a `Mono` return * Convert `Mono` to the `Continuation` resuming in the end of gateway call * Document `suspend fun` for `@MessagingGateway` * * Make `async` implicitly only for `suspend fun` * * Remove unused imports * * Verify sync and async `Flow` processing * Mention default sync behavior in the docs * * Improve reflection in the `CoroutinesUtils` * Fix language in docs Co-authored-by: Gary Russell <grussell@vmware.com> * * Rebase and revert blank lines around `include` in docs Co-authored-by: Gary Russell <grussell@vmware.com>
This commit is contained in:
@@ -110,32 +110,3 @@ public IntegrationFlow supplierFlow() {
|
||||
====
|
||||
|
||||
This function support is useful when used together with the https://cloud.spring.io/spring-cloud-function/[Spring Cloud Function] framework, where we have a function catalog and can refer to its member functions from an integration flow definition.
|
||||
|
||||
[[kotlin-functions-support]]
|
||||
==== Kotlin Lambdas
|
||||
|
||||
The Framework also has been improved to support Kotlin lambdas for functions, so now you can use a combination of the Kotlin language and Spring Integration flow definitions:
|
||||
|
||||
====
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
@Transformer(inputChannel = "functionServiceChannel")
|
||||
fun kotlinFunction(): (String) -> String {
|
||||
return { it.toUpperCase() }
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "messageConsumerServiceChannel")
|
||||
fun kotlinConsumer(): (Message<Any>) -> Unit {
|
||||
return { print(it) }
|
||||
}
|
||||
|
||||
@Bean
|
||||
@InboundChannelAdapter(value = "counterChannel",
|
||||
poller = [Poller(fixedRate = "10", maxMessagesPerPoll = "1")])
|
||||
fun kotlinSupplier(): () -> String {
|
||||
return { "baz" }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
@@ -771,6 +771,8 @@ mono.subscribe(invoice -> handleInvoice(invoice));
|
||||
|
||||
The calling thread continues, with `handleInvoice()` being called when the flow completes.
|
||||
|
||||
Also see <<./kotlin-functions.adoc#kotlin-coroutines,Kotlin Coroutines>> for more information.
|
||||
|
||||
===== Downstream Flows Returning an Asynchronous Type
|
||||
|
||||
As mentioned in the <<gateway-asynctaskexecutor>> section above, if you wish some downstream component to return a message with an async payload (`Future`, `Mono`, and others), you must explicitly set the async executor to `null` (or `""` when using XML configuration).
|
||||
|
||||
@@ -37,6 +37,8 @@ include::./kotlin-dsl.adoc[]
|
||||
|
||||
include::./system-management.adoc[]
|
||||
|
||||
include::./reactive-streams.adoc[]
|
||||
|
||||
include::./endpoint-summary.adoc[]
|
||||
|
||||
include::./amqp.adoc[]
|
||||
|
||||
88
src/reference/asciidoc/kotlin-functions.adoc
Normal file
88
src/reference/asciidoc/kotlin-functions.adoc
Normal file
@@ -0,0 +1,88 @@
|
||||
[[kotlin-functions-support]]
|
||||
=== Kotlin Support
|
||||
|
||||
The Framework also has been improved to support Kotlin lambdas for functions, so now you can use a combination of the Kotlin language and Spring Integration flow definitions:
|
||||
|
||||
====
|
||||
[source, kotlin]
|
||||
----
|
||||
@Bean
|
||||
@Transformer(inputChannel = "functionServiceChannel")
|
||||
fun kotlinFunction(): (String) -> String {
|
||||
return { it.toUpperCase() }
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ServiceActivator(inputChannel = "messageConsumerServiceChannel")
|
||||
fun kotlinConsumer(): (Message<Any>) -> Unit {
|
||||
return { print(it) }
|
||||
}
|
||||
|
||||
@Bean
|
||||
@InboundChannelAdapter(value = "counterChannel",
|
||||
poller = Poller(fixedRate = "10", maxMessagesPerPoll = "1"))
|
||||
fun kotlinSupplier(): () -> String {
|
||||
return { "baz" }
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[kotlin-coroutines]]
|
||||
==== Kotlin Coroutines
|
||||
|
||||
Starting with version 6.0, Spring Integration provides support for https://kotlinlang.org/docs/coroutines-guide.html[Kotlin Coroutines].
|
||||
Now `suspend` functions and `kotlinx.coroutines.Deferred` & `kotlinx.coroutines.flow.Flow` return types can be used for service methods:
|
||||
|
||||
====
|
||||
[source, kotlin]
|
||||
----
|
||||
@ServiceActivator(inputChannel = "suspendServiceChannel", outputChannel = "resultChannel")
|
||||
suspend fun suspendServiceFunction(payload: String) = payload.uppercase()
|
||||
|
||||
@ServiceActivator(inputChannel = "flowServiceChannel", outputChannel = "resultChannel", async = "true")
|
||||
fun flowServiceFunction(payload: String) =
|
||||
flow {
|
||||
for (i in 1..3) {
|
||||
emit("$payload #$i")
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The framework treats them as Reactive Streams interactions and uses `ReactiveAdapterRegistry` to convert to respective `Mono` and `Flux` reactor types.
|
||||
Such a function reply is processed then in the reply channel, if it is a `ReactiveStreamsSubscribableChannel`, or as a result of `CompletableFuture` in the respective callback.
|
||||
|
||||
NOTE: The functions with `Flow` result are not `async` by default on the `@ServiceActivator`, so `Flow` instance is produced as a reply message payload.
|
||||
It is the target application's responsibility to process this object as a coroutine or convert it to `Flux`, respectively.
|
||||
|
||||
The `@MessagingGateway` interface methods also can be marked with a `suspend` modifier when declared in Kotlin.
|
||||
The framework utilizes a `Mono` internally to perform request-reply using the downstream flow.
|
||||
Such a `Mono` result is processed by the `MonoKt.awaitSingleOrNull()` API internally to fulfil a `kotlin.coroutines.Continuation` argument fo the called `suspend` function of the gateway:
|
||||
|
||||
====
|
||||
[source, kotlin]
|
||||
----
|
||||
@MessagingGateway(defaultRequestChannel = "suspendRequestChannel")
|
||||
interface SuspendFunGateway {
|
||||
|
||||
suspend fun suspendGateway(payload: String): String
|
||||
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This method has to be called as a coroutine according to Kotlin language requirements:
|
||||
|
||||
====
|
||||
[source, kotlin]
|
||||
----
|
||||
@Autowired
|
||||
private lateinit var suspendFunGateway: SuspendFunGateway
|
||||
|
||||
fun someServiceMethod() {
|
||||
runBlocking {
|
||||
val reply = suspendFunGateway.suspendGateway("test suspend gateway")
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -19,4 +19,6 @@ include::./handler-advice.adoc[]
|
||||
include::./logging-adapter.adoc[]
|
||||
|
||||
include::./functions-support.adoc[]
|
||||
|
||||
include::./kotlin-functions.adoc[]
|
||||
// BE SURE TO PRECEDE ALL include:: with a blank line - see https://asciidoctor.org/docs/user-manual/#include-partitioning
|
||||
|
||||
@@ -49,6 +49,8 @@ With a `ReactiveStreamsSubscribableChannel` for the `outputChannel`, there is no
|
||||
|
||||
See <<./service-activator.adoc#async-service-activator,Asynchronous Service Activator>> for more information.
|
||||
|
||||
Also see <<./kotlin-functions.adoc#kotlin-coroutines,Kotlin Coroutines>> for more information.
|
||||
|
||||
=== `FluxMessageChannel` and `ReactiveStreamsConsumer`
|
||||
|
||||
The `FluxMessageChannel` is a combined implementation of `MessageChannel` and `Publisher<Message<?>>`.
|
||||
|
||||
@@ -79,6 +79,12 @@ See <<./scripting.adoc#scripting,Scripting Support>> for more information.
|
||||
The Apache Cassandra Spring Integration Extensions project has been migrated as the `spring-integration-cassandra` module.
|
||||
See <<./cassandra.adoc#cassandra,Apache Cassandra Support>> for more information.
|
||||
|
||||
[[x6.0-kotlin-coroutines]]
|
||||
==== Kotlin Coroutines
|
||||
|
||||
Kotlin Coroutines support has been introduced to the framework.
|
||||
|
||||
See <<./kotlin-functions.adoc#kotlin-coroutines,Kotlin Coroutines>> for more information.
|
||||
[[x6.0-general]]
|
||||
=== General Changes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user