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 568d94b6c03..020a00064c9 100644 --- a/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java +++ b/spring-core/src/main/java/org/springframework/core/CoroutinesUtils.java @@ -134,9 +134,10 @@ public abstract class CoroutinesUtils { Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); - if (!type.isMarkedNullable() && + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && - KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass)) && + !JvmClassMappingKt.getJavaClass(kClass).isInstance(arg)) { arg = box(kClass, arg); } argMap.put(parameter, arg); @@ -166,9 +167,10 @@ public abstract class CoroutinesUtils { private static Object box(KClass kClass, @Nullable Object arg) { KFunction constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass)); KType type = constructor.getParameters().get(0).getType(); - if (!type.isMarkedNullable() && + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass parameterClass && - KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) { + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass)) && + !JvmClassMappingKt.getJavaClass(parameterClass).isInstance(arg)) { arg = box(parameterClass, arg); } if (!KCallablesJvm.isAccessible(constructor)) { 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 4cc2fdeefdb..0f3f4312d2c 100644 --- a/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt +++ b/spring-core/src/test/kotlin/org/springframework/core/CoroutinesUtilsTests.kt @@ -236,6 +236,13 @@ class CoroutinesUtilsTests { Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo") } + @Test + suspend fun invokeSuspendingFunctionWithNullableValueClassParameterAndUnderlyingValue() { + val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithNullableValueClass") } + val mono = CoroutinesUtils.invokeSuspendingFunction(method, this, "foo", null) as Mono + Assertions.assertThat(mono.awaitSingleOrNull()).isEqualTo("foo") + } + @Test suspend fun invokeSuspendingFunctionWithNullableValueClassParameter() { val method = CoroutinesUtilsTests::class.java.declaredMethods.first { it.name.startsWith("suspendingFunctionWithNullableValueClass") } 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 6dca729698e..1a1ef3a011a 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 @@ -316,9 +316,10 @@ public class InvocableHandlerMethod extends HandlerMethod { Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); - if (!type.isMarkedNullable() && + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && - KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass)) && + !JvmClassMappingKt.getJavaClass(kClass).isInstance(arg)) { arg = box(kClass, arg); } argMap.put(parameter, arg); @@ -337,9 +338,10 @@ public class InvocableHandlerMethod extends HandlerMethod { private static Object box(KClass kClass, @Nullable Object arg) { KFunction constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass)); KType type = constructor.getParameters().get(0).getType(); - if (!type.isMarkedNullable() && + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass parameterClass && - KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) { + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass)) && + !JvmClassMappingKt.getJavaClass(parameterClass).isInstance(arg)) { arg = box(parameterClass, arg); } if (!KCallablesJvm.isAccessible(constructor)) { 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 b7da2b669f2..a3dbd8f91a4 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 @@ -155,6 +155,13 @@ class InvocableHandlerMethodKotlinTests { Assertions.assertThat(value).isEqualTo(1L) } + @Test + fun valueClassWithNullableAndUnderlyingValue() { + composite.addResolver(StubArgumentResolver(LongValueClass::class.java, 1L)) + val value = getInvocable(ValueClassHandler::valueClassWithNullable.javaMethod!!).invokeForRequest(request, null) + Assertions.assertThat(value).isEqualTo(1L) + } + @Test fun valueClassWithNullable() { composite.addResolver(StubArgumentResolver(LongValueClass::class.java, null)) @@ -215,6 +222,14 @@ class InvocableHandlerMethodKotlinTests { StepVerifier.create(value as Mono).verifyComplete() } + @Test + fun suspendingValueClassWithNullableAndUnderlyingValue() { + composite.addResolver(ContinuationHandlerMethodArgumentResolver()) + composite.addResolver(StubArgumentResolver(LongValueClass::class.java, 1L)) + val value = getInvocable(SuspendingValueClassHandler::valueClassWithNullable.javaMethod!!).invokeForRequest(request, null) + StepVerifier.create(value as Mono).expectNext(1L).verifyComplete() + } + @Test fun suspendingValueClassWithPrivateConstructor() { composite.addResolver(ContinuationHandlerMethodArgumentResolver()) 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 c06ea62a2aa..11e312bbe8b 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 @@ -356,9 +356,10 @@ public class InvocableHandlerMethod extends HandlerMethod { Object arg = args[index]; if (!(parameter.isOptional() && arg == null)) { KType type = parameter.getType(); - if (!type.isMarkedNullable() && + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass kClass && - KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass))) { + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(kClass)) && + !JvmClassMappingKt.getJavaClass(kClass).isInstance(arg)) { arg = box(kClass, arg); } argMap.put(parameter, arg); @@ -378,9 +379,10 @@ public class InvocableHandlerMethod extends HandlerMethod { private static Object box(KClass kClass, @Nullable Object arg) { KFunction constructor = Objects.requireNonNull(KClasses.getPrimaryConstructor(kClass)); KType type = constructor.getParameters().get(0).getType(); - if (!type.isMarkedNullable() && + if (!(type.isMarkedNullable() && arg == null) && type.getClassifier() instanceof KClass parameterClass && - KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass))) { + KotlinDetector.isInlineClass(JvmClassMappingKt.getJavaClass(parameterClass)) && + !JvmClassMappingKt.getJavaClass(parameterClass).isInstance(arg)) { arg = box(parameterClass, arg); } if (!KCallablesJvm.isAccessible(constructor)) { 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 9e41a222399..f8041148619 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 @@ -258,6 +258,14 @@ class InvocableHandlerMethodKotlinTests { assertHandlerResultValue(result, "1") } + @Test + fun valueClassWithNullableAndUnderlyingValue() { + this.resolvers.add(stubResolver(1L, LongValueClass::class.java)) + val method = ValueClassController::valueClassWithNullable.javaMethod!! + val result = invoke(ValueClassController(), method) + assertHandlerResultValue(result, "1") + } + @Test fun valueClassWithNullable() { this.resolvers.add(stubResolver(null, LongValueClass::class.java)) @@ -320,6 +328,14 @@ class InvocableHandlerMethodKotlinTests { assertHandlerResultValue(result, "null") } + @Test + fun suspendingValueClassWithNullableAndUnderlyingValue() { + this.resolvers.add(stubResolver(1L, LongValueClass::class.java)) + val method = SuspendingValueClassController::valueClassWithNullable.javaMethod!! + val result = invoke(SuspendingValueClassController(), method) + assertHandlerResultValue(result, "1") + } + @Test fun suspendingValueClassWithPrivateConstructor() { this.resolvers.add(stubResolver(1L, Long::class.java)) @@ -590,4 +606,4 @@ class InvocableHandlerMethodKotlinTests { } class CustomException(message: String) : Throwable(message) -} \ No newline at end of file +}