Avoid Kotlin reflection for non-suspending return types

Previously, KotlinDelegate.getReturnType() and getGenericReturnType()
resolved the KFunction of every Kotlin method, which materializes the
members of the declaring KClass and is costly at startup. Kotlin
reflection is only needed to resolve the return type of suspending
functions, so both methods now only use it when
KotlinDetector.isSuspendingFunction() returns true.

Closes gh-37300

Signed-off-by: Sébastien Deleuze <sdeleuze@users.noreply.github.com>
This commit is contained in:
Sébastien Deleuze
2026-10-05 10:53:14 +02:00
parent 14dc767dd6
commit 90400757e8
2 changed files with 53 additions and 17 deletions
@@ -972,14 +972,16 @@ public class MethodParameter {
* functions via Kotlin reflection.
*/
private static Type getGenericReturnType(Method method) {
try {
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
if (function != null && function.isSuspend()) {
return ReflectJvmMapping.getJavaType(function.getReturnType());
if (KotlinDetector.isSuspendingFunction(method)) {
try {
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
if (function != null && function.isSuspend()) {
return ReflectJvmMapping.getJavaType(function.getReturnType());
}
}
catch (UnsupportedOperationException ex) {
// probably a synthetic class - let's use java reflection instead
}
}
catch (UnsupportedOperationException ex) {
// probably a synthetic class - let's use java reflection instead
}
return method.getGenericReturnType();
}
@@ -989,18 +991,20 @@ public class MethodParameter {
* functions via Kotlin reflection.
*/
private static Class<?> getReturnType(Method method) {
try {
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
if (function != null && function.isSuspend()) {
Type paramType = ReflectJvmMapping.getJavaType(function.getReturnType());
if (paramType == Unit.class) {
paramType = void.class;
if (KotlinDetector.isSuspendingFunction(method)) {
try {
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
if (function != null && function.isSuspend()) {
Type paramType = ReflectJvmMapping.getJavaType(function.getReturnType());
if (paramType == Unit.class) {
paramType = void.class;
}
return ResolvableType.forType(paramType).resolve(method.getReturnType());
}
return ResolvableType.forType(paramType).resolve(method.getReturnType());
}
}
catch (UnsupportedOperationException ex) {
// probably a synthetic class - let's use java reflection instead
catch (UnsupportedOperationException ex) {
// probably a synthetic class - let's use java reflection instead
}
}
return method.getReturnType();
}
@@ -18,6 +18,8 @@ package org.springframework.core
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mockStatic
import org.mockito.stubbing.Answer
import java.lang.reflect.Method
import java.lang.reflect.TypeVariable
import kotlin.coroutines.Continuation
@@ -114,6 +116,30 @@ class MethodParameterKotlinTests {
assertThat(returnGenericParameterType("suspendFun8")).isEqualTo(Any::class.java)
}
@Test
fun `Regular function return type`() {
assertThat(returnParameterType("regularFun")).isEqualTo(Producer::class.java)
assertThat(returnGenericParameterTypeName("regularFun")).isEqualTo("org.springframework.core.Producer<java.lang.Number>")
}
@Test
fun `Regular function return type does not use Kotlin reflection`() {
val methodParameter = returnMethodParameter("regularFun")
mockStatic(Class.forName("kotlin.reflect.jvm.ReflectJvmMapping"), Answer<Any> {
throw AssertionError("Kotlin reflection should not be used for regular functions")
}).use {
assertThat(methodParameter.parameterType).isEqualTo(Producer::class.java)
assertThat(methodParameter.genericParameterType.typeName)
.isEqualTo("org.springframework.core.Producer<java.lang.Number>")
}
}
@Test
fun `Regular function with Continuation parameter return type`() {
assertThat(returnParameterType("regularFunWithContinuation")).isEqualTo(Producer::class.java)
assertThat(returnGenericParameterTypeName("regularFunWithContinuation")).isEqualTo("org.springframework.core.Producer<java.lang.Number>")
}
@Test
fun `Parameter name for regular function`() {
val methodParameter = returnMethodParameter("nullable", 0)
@@ -186,6 +212,12 @@ class MethodParameterKotlinTests {
@Suppress("unused", "unused_parameter")
suspend fun suspendFun8(p1: String): Any? = TODO()
@Suppress("unused", "unused_parameter")
fun regularFun(p1: String): Producer<Number> = TODO()
@Suppress("unused", "unused_parameter")
fun regularFunWithContinuation(p1: String, continuation: Continuation<Number>): Producer<Number> = TODO()
}
interface Producer<out T>