From bf0819390f05ebfad53fe16101e940bd85b94ff7 Mon Sep 17 00:00:00 2001 From: T45K Date: Thu, 14 Dec 2023 21:52:57 +0900 Subject: [PATCH] Support Kotlin value classes as suspending function arguments Similar to gh-31698 but for Coroutines. See gh-31846 --- .../springframework/core/CoroutinesUtils.java | 14 +++++++++++++- .../core/CoroutinesUtilsTests.kt | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java index e25a8be6d2ff..42e6c9692fa4 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -18,6 +18,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Map; import java.util.Objects; @@ -40,6 +41,7 @@ import kotlinx.coroutines.reactor.MonoKt; import kotlinx.coroutines.reactor.ReactorFlowKt; import org.reactivestreams.Publisher; +import org.springframework.util.ReflectionUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -55,6 +57,9 @@ */ public abstract class CoroutinesUtils { + private static final ReflectionUtils.MethodFilter boxImplFilter = + (method -> method.isSynthetic() && Modifier.isStatic(method.getModifiers()) && method.getName().equals("box-impl")); + /** * Convert a {@link Deferred} instance to a {@link Mono}. */ @@ -115,7 +120,14 @@ public static Publisher invokeSuspendingFunction(CoroutineContext context, Me case INSTANCE -> argMap.put(parameter, target); case VALUE -> { if (!parameter.isOptional() || args[index] != null) { - argMap.put(parameter, args[index]); + if (parameter.getType().getClassifier() instanceof KClass kClass && kClass.isValue()) { + Class javaClass = JvmClassMappingKt.getJavaClass(kClass); + Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(javaClass, boxImplFilter); + Assert.state(methods.length == 1, "Unable to find a single box-impl synthetic static method in " + javaClass.getName()); + argMap.put(parameter, ReflectionUtils.invokeMethod(methods[0], null, args[index])); + } else { + argMap.put(parameter, args[index]); + } } index++; } diff --git a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt index 9a7029c7f137..b5fad73d9ccd 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt @@ -145,6 +145,15 @@ class CoroutinesUtilsTests { } } + @Test + fun invokeSuspendingFunctionWithValueClassParameter() { + val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClass") } + val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo") + } + } + suspend fun suspendingFunction(value: String): String { delay(1) return value @@ -172,4 +181,12 @@ class CoroutinesUtilsTests { return null } + suspend fun suspendingFunctionWithValueClass(value: ValueClass): String { + delay(1) + return value.value + } + + @JvmInline + value class ValueClass(val value: String) + }