Move Kotlin value class unboxing to InvocableHandlerMethod

Before this commit, in Spring Framework 6.2, Kotlin value class
unboxing was done at CoroutinesUtils level, which is a good fit
for InvocableHandlerMethod use case, but not for other ones like
AopUtils.

This commit moves such unboxing to InvocableHandlerMethod in
order to keep the HTTP response body support while fixing other
regressions.

Closes gh-33943
This commit is contained in:
Sébastien Deleuze
2024-11-27 16:27:37 +01:00
parent ea3bd7ae0c
commit 1aede291bb
6 changed files with 320 additions and 62 deletions

View File

@@ -44,7 +44,6 @@ import kotlinx.coroutines.reactor.ReactorFlowKt;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -109,7 +108,7 @@ public abstract class CoroutinesUtils {
* @throws IllegalArgumentException if {@code method} is not a suspending function
* @since 6.0
*/
@SuppressWarnings({"deprecation", "DataFlowIssue", "NullAway"})
@SuppressWarnings({"DataFlowIssue", "NullAway"})
public static Publisher<?> invokeSuspendingFunction(
CoroutineContext context, Method method, @Nullable Object target, @Nullable Object... args) {
@@ -146,7 +145,7 @@ public abstract class CoroutinesUtils {
}
return KCallables.callSuspendBy(function, argMap, continuation);
})
.handle(CoroutinesUtils::handleResult)
.filter(result -> result != Unit.INSTANCE)
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
KType returnType = function.getReturnType();
@@ -166,22 +165,4 @@ public abstract class CoroutinesUtils {
return ReactorFlowKt.asFlux(((Flow<?>) flow));
}
private static void handleResult(Object result, SynchronousSink<Object> sink) {
if (result == Unit.INSTANCE) {
sink.complete();
}
else if (KotlinDetector.isInlineClass(result.getClass())) {
try {
sink.next(result.getClass().getDeclaredMethod("unbox-impl").invoke(result));
sink.complete();
}
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
sink.error(ex);
}
}
else {
sink.next(result);
sink.complete();
}
}
}

View File

@@ -192,7 +192,7 @@ class CoroutinesUtilsTests {
@Test
fun invokeSuspendingFunctionWithValueClassParameter() {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClass") }
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassParameter") }
val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
@@ -204,7 +204,16 @@ class CoroutinesUtilsTests {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassReturnValue") }
val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, null) as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo("foo")
Assertions.assertThat(mono.awaitSingle()).isEqualTo(ValueClass("foo"))
}
}
@Test
fun invokeSuspendingFunctionWithResultOfUnitReturnValue() {
val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithResultOfUnitReturnValue") }
val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, null) as Mono
runBlocking {
Assertions.assertThat(mono.awaitSingle()).isEqualTo(Result.success(Unit))
}
}
@@ -314,7 +323,7 @@ class CoroutinesUtilsTests {
return null
}
suspend fun suspendingFunctionWithValueClass(value: ValueClass): String {
suspend fun suspendingFunctionWithValueClassParameter(value: ValueClass): String {
delay(1)
return value.value
}
@@ -324,6 +333,11 @@ class CoroutinesUtilsTests {
return ValueClass("foo")
}
suspend fun suspendingFunctionWithResultOfUnitReturnValue(): Result<Unit> {
delay(1)
return Result.success(Unit)
}
suspend fun suspendingFunctionWithValueClassWithInit(value: ValueClassWithInit): String {
delay(1)
return value.value