diff --git a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java index 31d580efa3ec..751518bf38d2 100644 --- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -202,8 +202,12 @@ private static boolean isResolvedTypeMatch(Method genericMethod, Method candidat private static boolean checkResolvedTypeMatch(Method genericMethod, Method candidateMethod, Class clazz) { // First, compare return type. ResolvableType genericReturnType = ResolvableType.forMethodReturnType(genericMethod, clazz); + // Kotlin suspending functions expose Object as their JVM return type, so compare their logical return types. + Class candidateReturnType = (KotlinDetector.isSuspendingFunction(genericMethod) && + KotlinDetector.isSuspendingFunction(candidateMethod) ? + ResolvableType.forMethodReturnType(candidateMethod, clazz).toClass() : candidateMethod.getReturnType()); if (!ClassUtils.resolvePrimitiveIfNecessary(genericReturnType.toClass()).isAssignableFrom( - ClassUtils.resolvePrimitiveIfNecessary(candidateMethod.getReturnType()))) { + ClassUtils.resolvePrimitiveIfNecessary(candidateReturnType))) { return false; } Class[] candidateParameters = candidateMethod.getParameterTypes(); diff --git a/spring-core/src/test/kotlin/org/springframework/core/BridgeMethodResolverKotlinTests.kt b/spring-core/src/test/kotlin/org/springframework/core/BridgeMethodResolverKotlinTests.kt index b73024af1673..57298207a907 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/BridgeMethodResolverKotlinTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/BridgeMethodResolverKotlinTests.kt @@ -16,6 +16,8 @@ package org.springframework.core +import kotlin.coroutines.Continuation + import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -47,6 +49,22 @@ class BridgeMethodResolverKotlinTests { assertThat(BridgeMethodResolver.findBridgedMethod(unbridged)).`as`("Unbridged method not returned directly").isEqualTo(unbridged) assertThat(BridgeMethodResolver.findBridgedMethod(bridged)).`as`("Incorrect bridged method returned").isEqualTo(unbridged) } + + @Test + fun findBridgedMethodForMultiLevelGenericSuspendOverride() { + val unbridged = SuspendRepository::class.java.getDeclaredMethod( + "process", SampleValue::class.java, Continuation::class.java) + val specializedBridge = SuspendRepository::class.java.getDeclaredMethod( + "process", SpecializedValue::class.java, Continuation::class.java) + val rootBridge = SuspendRepository::class.java.getDeclaredMethod( + "process", RootValue::class.java, Continuation::class.java) + assertThat(unbridged.isBridge).isFalse + assertThat(specializedBridge.isBridge).isTrue + assertThat(rootBridge.isBridge).isTrue + + assertThat(BridgeMethodResolver.findBridgedMethod(specializedBridge)).isEqualTo(unbridged) + assertThat(BridgeMethodResolver.findBridgedMethod(rootBridge)).isEqualTo(unbridged) + } } interface GenericInterface { @@ -74,3 +92,23 @@ class GenericRepository : AbstractGenericClass() { } } +interface RootValue + +interface SpecializedValue : RootValue + +class SampleValue : SpecializedValue + +interface SuspendProcessor { + + suspend fun process(value: S): S +} + +abstract class AbstractSuspendProcessor : SuspendProcessor { + + override suspend fun process(value: S): S = value +} + +class SuspendRepository : AbstractSuspendProcessor() { + + override suspend fun process(value: S): S = value +}