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:
@@ -18,11 +18,14 @@ package org.springframework.web.method.support;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.jvm.KCallablesJvm;
|
||||
@@ -35,8 +38,10 @@ import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
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.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.support.SessionStatus;
|
||||
@@ -60,6 +65,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private static final Class<?>[] EMPTY_GROUPS = new Class<?>[0];
|
||||
|
||||
private static final ReflectionUtils.MethodFilter boxImplFilter =
|
||||
(method -> method.isSynthetic() && Modifier.isStatic(method.getModifiers()) && method.getName().equals("box-impl"));
|
||||
|
||||
|
||||
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
|
||||
|
||||
@@ -310,7 +318,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++;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,20 @@ class InvocableHandlerMethodKotlinTests {
|
||||
Assertions.assertThat(value).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueClass() {
|
||||
composite.addResolver(StubArgumentResolver(Long::class.java, 1L))
|
||||
val value = getInvocable(Long::class.java).invokeForRequest(request, null)
|
||||
Assertions.assertThat(value).isEqualTo(1L)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun valueClassDefaultValue() {
|
||||
composite.addResolver(StubArgumentResolver(Double::class.java))
|
||||
val value = getInvocable(Double::class.java).invokeForRequest(request, null)
|
||||
Assertions.assertThat(value).isEqualTo(3.1)
|
||||
}
|
||||
|
||||
private fun getInvocable(vararg argTypes: Class<*>): InvocableHandlerMethod {
|
||||
val method = ResolvableMethod.on(Handler::class.java).argTypes(*argTypes).resolveMethod()
|
||||
val handlerMethod = InvocableHandlerMethod(Handler(), method)
|
||||
@@ -116,6 +130,18 @@ class InvocableHandlerMethodKotlinTests {
|
||||
fun nullable(arg: String?): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun valueClass(limit: LongValueClass) =
|
||||
limit.value
|
||||
|
||||
fun valueClass(limit: DoubleValueClass = DoubleValueClass(3.1)) =
|
||||
limit.value
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class LongValueClass(val value: Long)
|
||||
|
||||
@JvmInline
|
||||
value class DoubleValueClass(val value: Double)
|
||||
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user