Support Kotlin parameter default values in handler methods
This commit adds support for Kotlin parameter default values in handler methods. It allows to write: @RequestParam value: String = "default" as an alternative to: @RequestParam(defaultValue = "default") value: String Both Spring MVC and WebFlux are supported, including on suspending functions. Closes gh-21139
This commit is contained in:
@@ -18,6 +18,7 @@ package org.springframework.core;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import kotlin.Unit;
|
||||
@@ -26,6 +27,7 @@ import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KClassifier;
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.full.KCallables;
|
||||
import kotlin.reflect.jvm.KCallablesJvm;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
@@ -42,6 +44,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Utilities for working with Kotlin Coroutines.
|
||||
@@ -104,8 +107,22 @@ public abstract class CoroutinesUtils {
|
||||
if (method.isAccessible() && !KCallablesJvm.isAccessible(function)) {
|
||||
KCallablesJvm.setAccessible(function, true);
|
||||
}
|
||||
Mono<Object> mono = MonoKt.mono(context, (scope, continuation) ->
|
||||
KCallables.callSuspend(function, getSuspendedFunctionArgs(method, target, args), continuation))
|
||||
Mono<Object> mono = MonoKt.mono(context, (scope, continuation) -> {
|
||||
Map<KParameter, Object> argMap = CollectionUtils.newHashMap(args.length + 1);
|
||||
int index = 0;
|
||||
for (KParameter parameter : function.getParameters()) {
|
||||
switch (parameter.getKind()) {
|
||||
case INSTANCE -> argMap.put(parameter, target);
|
||||
case VALUE -> {
|
||||
if (!parameter.isOptional() || args[index] != null) {
|
||||
argMap.put(parameter, args[index]);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return KCallables.callSuspendBy(function, argMap, continuation);
|
||||
})
|
||||
.filter(result -> !Objects.equals(result, Unit.INSTANCE))
|
||||
.onErrorMap(InvocationTargetException.class, InvocationTargetException::getTargetException);
|
||||
|
||||
@@ -125,14 +142,6 @@ public abstract class CoroutinesUtils {
|
||||
return mono;
|
||||
}
|
||||
|
||||
private static Object[] getSuspendedFunctionArgs(Method method, Object target, Object... args) {
|
||||
int length = (args.length == method.getParameterCount() - 1 ? args.length + 1 : args.length);
|
||||
Object[] functionArgs = new Object[length];
|
||||
functionArgs[0] = target;
|
||||
System.arraycopy(args, 0, functionArgs, 1, length - 1);
|
||||
return functionArgs;
|
||||
}
|
||||
|
||||
private static Flux<?> asFlux(Object flow) {
|
||||
return ReactorFlowKt.asFlux(((Flow<?>) flow));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -38,6 +38,8 @@ class KotlinMethodParameterTests {
|
||||
|
||||
private val nonNullableMethod = javaClass.getMethod("nonNullable", String::class.java)
|
||||
|
||||
private val withDefaultValueMethod: Method = javaClass.getMethod("withDefaultValue", String::class.java)
|
||||
|
||||
private val innerClassConstructor = InnerClass::class.java.getConstructor(KotlinMethodParameterTests::class.java)
|
||||
|
||||
private val innerClassWithParametersConstructor = InnerClassWithParameter::class.java
|
||||
@@ -52,6 +54,16 @@ class KotlinMethodParameterTests {
|
||||
assertThat(MethodParameter(nonNullableMethod, 0).isOptional).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Method parameter with default value`() {
|
||||
assertThat(MethodParameter(withDefaultValueMethod, 0).isOptional).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Method parameter without default value`() {
|
||||
assertThat(MethodParameter(nonNullableMethod, 0).isOptional).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Method return type nullability`() {
|
||||
assertThat(MethodParameter(nullableMethod, -1).isOptional).isTrue()
|
||||
@@ -123,6 +135,8 @@ class KotlinMethodParameterTests {
|
||||
@Suppress("unused_parameter")
|
||||
fun nonNullable(nonNullable: String): Int = 42
|
||||
|
||||
fun withDefaultValue(withDefaultValue: String = "default") = withDefaultValue
|
||||
|
||||
inner class InnerClass
|
||||
|
||||
@Suppress("unused_parameter")
|
||||
|
||||
Reference in New Issue
Block a user