Improve WebFlux suspending handler method support

Support for suspending handler methods introduced in Spring
Framework 5.2 M1 does not detect types correctly and does not
support suspending handler methods returning Flow which is a
common use case with WebClient.

This commit fixes these issues and adds Coroutines integration
tests.

Closes gh-22820
Closes gh-22827
This commit is contained in:
Sebastien Deleuze
2019-04-23 11:09:41 +02:00
parent dab90cb7cc
commit aee2df8919
3 changed files with 184 additions and 7 deletions

View File

@@ -19,8 +19,12 @@ package org.springframework.core
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.reactive.awaitFirstOrNull
import kotlinx.coroutines.reactor.mono
@@ -29,6 +33,8 @@ import reactor.core.publisher.onErrorMap
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import kotlin.reflect.full.callSuspend
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.starProjectedType
import kotlin.reflect.jvm.kotlinFunction
/**
@@ -50,18 +56,29 @@ internal fun <T: Any> monoToDeferred(source: Mono<T>) =
GlobalScope.async(Dispatchers.Unconfined) { source.awaitFirstOrNull() }
/**
* Invoke an handler method converting suspending method to [Mono] if necessary.
* Invoke an handler method converting suspending method to [Mono] or [Flow] if necessary.
*
* @author Sebastien Deleuze
* @since 5.2
*/
@Suppress("UNCHECKED_CAST")
@FlowPreview
internal fun invokeHandlerMethod(method: Method, bean: Any, vararg args: Any?): Any? {
val function = method.kotlinFunction!!
return if (function.isSuspend) {
GlobalScope.mono(Dispatchers.Unconfined) {
function.callSuspend(bean, *args.sliceArray(0..(args.size-2)))
.let { if (it == Unit) null else it} }
.onErrorMap(InvocationTargetException::class) { it.targetException }
if (function.returnType.isSubtypeOf(Flow::class.starProjectedType)) {
flow {
(function.callSuspend(bean, *args.sliceArray(0..(args.size-2))) as Flow<*>).collect {
emit(it)
}
}
}
else {
GlobalScope.mono(Dispatchers.Unconfined) {
function.callSuspend(bean, *args.sliceArray(0..(args.size-2)))
.let { if (it == Unit) null else it}
}.onErrorMap(InvocationTargetException::class) { it.targetException }
}
}
else {
function.call(bean, *args)