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 8ad247f15c38..24198014dac4 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -130,7 +130,9 @@ public static Publisher invokeSuspendingFunction( KType type = parameter.getType(); if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - arg = KClasses.getPrimaryConstructor(kClass).call(arg); + KFunction valueClassConstructor = KClasses.getPrimaryConstructor(kClass); + KCallablesJvm.setAccessible(valueClassConstructor, true); + arg = valueClassConstructor.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 86a12075be46..6aee679df09f 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt @@ -28,6 +28,8 @@ import reactor.core.publisher.Mono import reactor.test.StepVerifier import kotlin.coroutines.Continuation import kotlin.coroutines.coroutineContext +import kotlin.reflect.full.primaryConstructor +import kotlin.reflect.jvm.isAccessible /** * Kotlin tests for [CoroutinesUtils]. @@ -206,6 +208,15 @@ class CoroutinesUtilsTests { } } + @Test + fun invokeSuspendingFunctionWithValueClassWithPrivateConstructorParameter() { + val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithValueClassWithPrivateConstructor") } + val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono + runBlocking { + Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo") + } + } + @Test fun invokeSuspendingFunctionWithExtension() { val method = CoroutinesUtilsTests::class.java.getDeclaredMethod("suspendingFunctionWithExtension", @@ -293,6 +304,11 @@ class CoroutinesUtilsTests { return value?.value } + suspend fun suspendingFunctionWithValueClassWithPrivateConstructor(value: ValueClassWithPrivateConstructor): String? { + delay(1) + return value.value + } + suspend fun CustomException.suspendingFunctionWithExtension(): String { delay(1) return "${this.message}" @@ -331,6 +347,13 @@ class CoroutinesUtilsTests { } } + @JvmInline + value class ValueClassWithPrivateConstructor private constructor(val value: String) { + companion object { + fun from(value: String) = ValueClassWithPrivateConstructor(value) + } + } + class CustomException(message: String) : Throwable(message) } 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 40c8414079ba..214d18e0faa1 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 @@ -319,7 +319,9 @@ public static Object invokeFunction(Method method, Object target, Object[] args) KType type = parameter.getType(); if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - arg = KClasses.getPrimaryConstructor(kClass).call(arg); + KFunction valueClassConstructor = KClasses.getPrimaryConstructor(kClass); + KCallablesJvm.setAccessible(valueClassConstructor, true); + arg = valueClassConstructor.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 513437eee696..82cac5450d70 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 @@ -112,6 +112,13 @@ class InvocableHandlerMethodKotlinTests { Assertions.assertThat(value).isNull() } + @Test + fun valueClassWithPrivateConstructor() { + composite.addResolver(StubArgumentResolver(Char::class.java, 'a')) + val value = getInvocable(ValueClassHandler::class.java, Char::class.java).invokeForRequest(request, null) + Assertions.assertThat(value).isEqualTo('a') + } + @Test fun propertyAccessor() { val value = getInvocable(PropertyAccessorHandler::class.java).invokeForRequest(request, null) @@ -191,6 +198,8 @@ class InvocableHandlerMethodKotlinTests { fun valueClassWithNullable(limit: LongValueClass?) = limit?.value + fun valueClassWithPrivateConstructor(limit: ValueClassWithPrivateConstructor) = + limit.value } private class PropertyAccessorHandler { @@ -238,6 +247,13 @@ class InvocableHandlerMethodKotlinTests { } } + @JvmInline + value class ValueClassWithPrivateConstructor private constructor(val value: Char) { + companion object { + fun from(value: Char) = ValueClassWithPrivateConstructor(value) + } + } + class CustomException(message: String) : Throwable(message) } 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 89fd48c2f599..57c32db1a181 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 @@ -332,7 +332,9 @@ public static Object invokeFunction(Method method, Object target, Object[] args, KType type = parameter.getType(); if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { - arg = KClasses.getPrimaryConstructor(kClass).call(arg); + KFunction valueClassConstructor = KClasses.getPrimaryConstructor(kClass); + KCallablesJvm.setAccessible(valueClassConstructor, true); + arg = valueClassConstructor.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 342c20d99f01..bccfa8930db1 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 @@ -222,6 +222,14 @@ class InvocableHandlerMethodKotlinTests { assertHandlerResultValue(result, "null") } + @Test + fun valueClassWithPrivateConstructor() { + this.resolvers.add(stubResolver(1L, Long::class.java)) + val method = ValueClassController::valueClassWithPrivateConstructor.javaMethod!! + val result = invoke(ValueClassController(), method, 1L) + assertHandlerResultValue(result, "1") + } + @Test fun propertyAccessor() { this.resolvers.add(stubResolver(null, String::class.java)) @@ -368,6 +376,8 @@ class InvocableHandlerMethodKotlinTests { fun valueClassWithNullable(limit: LongValueClass?) = "${limit?.value}" + fun valueClassWithPrivateConstructor(limit: ValueClassWithPrivateConstructor) = + "${limit.value}" } class PropertyAccessorController { @@ -416,5 +426,12 @@ class InvocableHandlerMethodKotlinTests { } } + @JvmInline + value class ValueClassWithPrivateConstructor private constructor(val value: Long) { + companion object { + fun from(value: Long) = ValueClassWithPrivateConstructor(value) + } + } + class CustomException(message: String) : Throwable(message) } \ No newline at end of file