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
This commit is contained in:
parent
917978cbc2
commit
12f01f9b5f
|
|
@ -21,7 +21,6 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlin.jvm.JvmClassMappingKt;
|
import kotlin.jvm.JvmClassMappingKt;
|
||||||
|
|
@ -306,8 +305,12 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public static Object invokeFunction(Method method, Object target, Object[] args) {
|
public static Object invokeFunction(Method method, Object target, Object[] args) throws InvocationTargetException, IllegalAccessException {
|
||||||
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)) {
|
if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {
|
||||||
KCallablesJvm.setAccessible(function, true);
|
KCallablesJvm.setAccessible(function, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,12 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
Assertions.assertThat(value).isEqualTo(3.1)
|
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 {
|
private fun getInvocable(clazz: Class<*>, vararg argTypes: Class<*>): InvocableHandlerMethod {
|
||||||
val method = ResolvableMethod.on(clazz).argTypes(*argTypes).resolveMethod()
|
val method = ResolvableMethod.on(clazz).argTypes(*argTypes).resolveMethod()
|
||||||
val handlerMethod = InvocableHandlerMethod(clazz.constructors.first().newInstance(), method)
|
val handlerMethod = InvocableHandlerMethod(clazz.constructors.first().newInstance(), method)
|
||||||
|
|
@ -138,6 +144,12 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
limit.value
|
limit.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class PropertyAccessorHandler {
|
||||||
|
|
||||||
|
val prop: String
|
||||||
|
get() = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class LongValueClass(val value: Long)
|
value class LongValueClass(val value: Long)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import java.lang.reflect.Type;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
|
|
@ -306,7 +305,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public static Object invokeFunction(Method method, Object target, Object[] args, boolean isSuspendingFunction,
|
public static Object invokeFunction(Method method, Object target, Object[] args, boolean isSuspendingFunction,
|
||||||
ServerWebExchange exchange) {
|
ServerWebExchange exchange) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
|
||||||
if (isSuspendingFunction) {
|
if (isSuspendingFunction) {
|
||||||
Object coroutineContext = exchange.getAttribute(COROUTINE_CONTEXT_ATTRIBUTE);
|
Object coroutineContext = exchange.getAttribute(COROUTINE_CONTEXT_ATTRIBUTE);
|
||||||
|
|
@ -318,7 +317,11 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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)) {
|
if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {
|
||||||
KCallablesJvm.setAccessible(function, true);
|
KCallablesJvm.setAccessible(function, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test
|
||||||
import org.springframework.core.ReactiveAdapterRegistry
|
import org.springframework.core.ReactiveAdapterRegistry
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse
|
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.RequestParam
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus
|
import org.springframework.web.bind.annotation.ResponseStatus
|
||||||
import org.springframework.web.reactive.BindingContext
|
import org.springframework.web.reactive.BindingContext
|
||||||
|
|
@ -194,6 +195,14 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
assertHandlerResultValue(result, "3.1")
|
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? {
|
private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? {
|
||||||
return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5))
|
return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5))
|
||||||
|
|
@ -293,6 +302,13 @@ class InvocableHandlerMethodKotlinTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PropertyAccessorController {
|
||||||
|
|
||||||
|
val prop: String
|
||||||
|
@GetMapping("/")
|
||||||
|
get() = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class LongValueClass(val value: Long)
|
value class LongValueClass(val value: Long)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue