diff --git a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java index 34e2c6a9758f..8ad247f15c38 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -128,11 +128,9 @@ public static Publisher invokeSuspendingFunction( Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); - if (!(type.isMarkedNullable() && arg == null)) { - KClass kClass = (KClass) type.getClassifier(); - if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - arg = KClasses.getPrimaryConstructor(kClass).call(arg); - } + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass + && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { + arg = KClasses.getPrimaryConstructor(kClass).call(arg); } argMap.put(parameter, arg); } diff --git a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt index ad0dd07f54bd..86a12075be46 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt @@ -226,6 +226,16 @@ class CoroutinesUtilsTests { } } + @Test + fun invokeSuspendingFunctionWithGenericParameter() { + val method = GenericController::class.java.declaredMethods.first { it.name.startsWith("handle") } + val horse = Animal("horse") + val mono = CoroutinesUtils.invokeSuspendingFunction(method, AnimalController(), horse, null) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingle()).isEqualTo(horse.name) + } + } + suspend fun suspendingFunction(value: String): String { delay(1) return value @@ -293,6 +303,22 @@ class CoroutinesUtilsTests { return "${this.message}-$limit" } + interface Named { + val name: String + } + + data class Animal(override val name: String) : Named + + abstract class GenericController { + + suspend fun handle(named: T): String { + delay(1) + return named.name; + } + } + + private class AnimalController : GenericController() + @JvmInline value class ValueClass(val value: String) 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 8bd593934ad6..40c8414079ba 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 @@ -317,11 +317,9 @@ public static Object invokeFunction(Method method, Object target, Object[] args) Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); - if (!(type.isMarkedNullable() && arg == null)) { - KClass kClass = (KClass) type.getClassifier(); - if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - arg = KClasses.getPrimaryConstructor(kClass).call(arg); - } + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass + && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { + arg = KClasses.getPrimaryConstructor(kClass).call(arg); } argMap.put(parameter, arg); } 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 a71fceb1135e..513437eee696 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -25,7 +25,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletRequest import org.springframework.web.testfixture.servlet.MockHttpServletResponse /** - * Kotlin unit tests for {@link InvocableHandlerMethod}. + * Kotlin unit tests for [InvocableHandlerMethod]. * * @author Sebastien Deleuze */ @@ -134,6 +134,14 @@ class InvocableHandlerMethodKotlinTests { Assertions.assertThat(value).isEqualTo("foo-20") } + @Test + fun genericParameter() { + val horse = Animal("horse") + composite.addResolver(StubArgumentResolver(Animal::class.java, horse)) + val value = getInvocable(AnimalHandler::class.java, Named::class.java).invokeForRequest(request, null) + Assertions.assertThat(value).isEqualTo(horse.name) + } + 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) @@ -202,6 +210,19 @@ class InvocableHandlerMethodKotlinTests { } } + private abstract class GenericHandler { + + fun handle(named: T) = named.name + } + + private class AnimalHandler : GenericHandler() + + interface Named { + val name: String + } + + data class Animal(override val name: String) : Named + @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 890d2979cb87..f812d49d27e2 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 @@ -328,11 +328,9 @@ public static Object invokeFunction(Method method, Object target, Object[] args, Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); - if (!(type.isMarkedNullable() && arg == null)) { - KClass kClass = (KClass) type.getClassifier(); - if (KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - arg = KClasses.getPrimaryConstructor(kClass).call(arg); - } + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass + && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { + arg = KClasses.getPrimaryConstructor(kClass).call(arg); } argMap.put(parameter, arg); } 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 7891deea18a3..342c20d99f01 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 @@ -249,6 +249,15 @@ class InvocableHandlerMethodKotlinTests { assertHandlerResultValue(result, "foo-20") } + @Test + fun genericParameter() { + val horse = Animal("horse") + this.resolvers.add(stubResolver(horse)) + val method = AnimalController::handle.javaMethod!! + val result = invoke(AnimalController(), method, null) + assertHandlerResultValue(result, horse.name) + } + private fun invokeForResult(handler: Any, method: Method, vararg providedArgs: Any): HandlerResult? { return invoke(handler, method, *providedArgs).block(Duration.ofSeconds(5)) @@ -379,6 +388,19 @@ class InvocableHandlerMethodKotlinTests { } } + private abstract class GenericController { + + fun handle(named: T) = named.name + } + + private class AnimalController : GenericController() + + interface Named { + val name: String + } + + data class Animal(override val name: String) : Named + @JvmInline value class LongValueClass(val value: Long)