From 12f01f9b5f277499c2f395f98324cfea30e72390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Tue, 19 Dec 2023 12:24:13 +0100 Subject: [PATCH] Add support for @RequestMapping on Kotlin property accessors This commit refines InvocableHandlerMethod (both Servlet and Reactive variants) in order to support annotated property accessors as they translate into regular Java methods, instead of throwing a NullPointerException. Closes gh-31856 --- .../method/support/InvocableHandlerMethod.java | 9 ++++++--- .../support/InvocableHandlerMethodKotlinTests.kt | 12 ++++++++++++ .../result/method/InvocableHandlerMethod.java | 9 ++++++--- .../result/InvocableHandlerMethodKotlinTests.kt | 16 ++++++++++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index 2b5b0e8c571..ed160be6f55 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -21,7 +21,6 @@ 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; @@ -306,8 +305,12 @@ public class InvocableHandlerMethod extends HandlerMethod { @Nullable @SuppressWarnings("deprecation") - public static Object invokeFunction(Method method, Object target, Object[] args) { - KFunction function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method)); + public static Object invokeFunction(Method method, Object target, Object[] args) throws InvocationTargetException, IllegalAccessException { + KFunction function = ReflectJvmMapping.getKotlinFunction(method); + // For property accessors + if (function == null) { + return method.invoke(target, args); + } if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) { KCallablesJvm.setAccessible(function, true); } diff --git a/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt b/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt index 7e462c452bb..7ee5d15c88d 100644 --- a/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt +++ b/spring-web/src/test/kotlin/org/springframework/web/method/support/InvocableHandlerMethodKotlinTests.kt @@ -98,6 +98,12 @@ class InvocableHandlerMethodKotlinTests { Assertions.assertThat(value).isEqualTo(3.1) } + @Test + fun propertyAccessor() { + val value = getInvocable(PropertyAccessorHandler::class.java).invokeForRequest(request, null) + Assertions.assertThat(value).isEqualTo("foo") + } + private fun getInvocable(clazz: Class<*>, vararg argTypes: Class<*>): InvocableHandlerMethod { val method = ResolvableMethod.on(clazz).argTypes(*argTypes).resolveMethod() val handlerMethod = InvocableHandlerMethod(clazz.constructors.first().newInstance(), method) @@ -138,6 +144,12 @@ class InvocableHandlerMethodKotlinTests { limit.value } + private class PropertyAccessorHandler { + + val prop: String + get() = "foo" + } + @JvmInline value class LongValueClass(val value: Long) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java index b52a3b97da4..490e897091d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java @@ -24,7 +24,6 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Stream; import kotlin.Unit; @@ -306,7 +305,7 @@ public class InvocableHandlerMethod extends HandlerMethod { @Nullable @SuppressWarnings("deprecation") public static Object invokeFunction(Method method, Object target, Object[] args, boolean isSuspendingFunction, - ServerWebExchange exchange) { + ServerWebExchange exchange) throws InvocationTargetException, IllegalAccessException { if (isSuspendingFunction) { Object coroutineContext = exchange.getAttribute(COROUTINE_CONTEXT_ATTRIBUTE); @@ -318,7 +317,11 @@ public class InvocableHandlerMethod extends HandlerMethod { } } else { - KFunction function = Objects.requireNonNull(ReflectJvmMapping.getKotlinFunction(method)); + KFunction function = ReflectJvmMapping.getKotlinFunction(method); + // For property accessors + if (function == null) { + return method.invoke(target, args); + } if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) { KCallablesJvm.setAccessible(function, true); } diff --git a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/InvocableHandlerMethodKotlinTests.kt b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/InvocableHandlerMethodKotlinTests.kt index f253ea7df26..57ef009b935 100644 --- a/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/InvocableHandlerMethodKotlinTests.kt +++ b/spring-webflux/src/test/kotlin/org/springframework/web/reactive/result/InvocableHandlerMethodKotlinTests.kt @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test import org.springframework.core.ReactiveAdapterRegistry import org.springframework.http.HttpStatus import org.springframework.http.server.reactive.ServerHttpResponse +import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.ResponseStatus import org.springframework.web.reactive.BindingContext @@ -194,6 +195,14 @@ class InvocableHandlerMethodKotlinTests { assertHandlerResultValue(result, "3.1") } + @Test + fun propertyAccessor() { + this.resolvers.add(stubResolver(Mono.empty())) + val method = PropertyAccessorController::prop.getter.javaMethod!! + val result = invoke(PropertyAccessorController(), method) + assertHandlerResultValue(result, "foo") + } + private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? { return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5)) @@ -293,6 +302,13 @@ class InvocableHandlerMethodKotlinTests { } + class PropertyAccessorController { + + val prop: String + @GetMapping("/") + get() = "foo" + } + @JvmInline value class LongValueClass(val value: Long)