Box Kotlin value class parameters in web endpoint

In order to avoid "java.lang.IllegalArgumentException:
object is not an instance of declaring class" errors.

Closes gh-31698
This commit is contained in:
Sébastien Deleuze
2023-12-08 10:14:13 +01:00
parent b78aed99ea
commit 91b9a75371
4 changed files with 92 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ package org.springframework.web.reactive.result.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
@@ -28,6 +29,8 @@ import java.util.stream.Stream;
import kotlin.Unit;
import kotlin.coroutines.CoroutineContext;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.KCallablesJvm;
@@ -44,8 +47,10 @@ import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.method.MethodValidator;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext;
@@ -70,6 +75,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
private static final Object NO_ARG_VALUE = new Object();
private static final ReflectionUtils.MethodFilter boxImplFilter =
(method -> method.isSynthetic() && Modifier.isStatic(method.getModifiers()) && method.getName().equals("box-impl"));
private final HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
@@ -321,7 +329,15 @@ public class InvocableHandlerMethod extends HandlerMethod {
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++;
}

View File

@@ -178,6 +178,22 @@ class InvocableHandlerMethodKotlinTests {
assertHandlerResultValue(result, null)
}
@Test
fun valueClass() {
this.resolvers.add(stubResolver(1L))
val method = ValueClassController::valueClass.javaMethod!!
val result = invoke(ValueClassController(), method,1L)
assertHandlerResultValue(result, "1")
}
@Test
fun valueClassDefaultValue() {
this.resolvers.add(stubResolver(Mono.empty()))
val method = ValueClassController::valueClassWithDefault.javaMethod!!
val result = invoke(ValueClassController(), method)
assertHandlerResultValue(result, "3.1")
}
private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? {
return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5))
@@ -266,4 +282,20 @@ class InvocableHandlerMethodKotlinTests {
return null
}
}
class ValueClassController {
fun valueClass(limit: LongValueClass) =
"${limit.value}"
fun valueClassWithDefault(limit: DoubleValueClass = DoubleValueClass(3.1)) =
"${limit.value}"
}
@JvmInline
value class LongValueClass(val value: Long)
@JvmInline
value class DoubleValueClass(val value: Double)
}