diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java index 9124b8c23caf..564206809d40 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessor.java @@ -25,11 +25,13 @@ import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.annotation.ReflectiveProcessor; +import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.http.HttpEntity; import org.springframework.lang.Nullable; import org.springframework.stereotype.Controller; +import org.springframework.util.ReflectionUtils; /** * {@link ReflectiveProcessor} implementation for {@link Controller} and @@ -71,6 +73,11 @@ protected void registerTypeHints(ReflectionHints hints, Class type) { protected void registerMethodHints(ReflectionHints hints, Method method) { hints.registerMethod(method, ExecutableMode.INVOKE); + Class declaringClass = method.getDeclaringClass(); + if (KotlinDetector.isKotlinType(declaringClass)) { + ReflectionUtils.doWithMethods(declaringClass, m -> hints.registerMethod(m, ExecutableMode.INVOKE), + m -> m.getName().equals(method.getName() + "$default")); + } for (Parameter parameter : method.getParameters()) { registerParameterTypeHints(hints, MethodParameter.forParameter(parameter)); } diff --git a/spring-web/src/test/kotlin/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorKotlinTests.kt b/spring-web/src/test/kotlin/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorKotlinTests.kt new file mode 100644 index 000000000000..2c8c74d5c5f6 --- /dev/null +++ b/spring-web/src/test/kotlin/org/springframework/web/bind/annotation/ControllerMappingReflectiveProcessorKotlinTests.kt @@ -0,0 +1,53 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.bind.annotation + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Test +import org.springframework.aot.hint.* + +/** + * Kotlin tests for {@link ControllerMappingReflectiveProcessor}. + * + * @author Sebastien Deleuze + */ +class ControllerMappingReflectiveProcessorKotlinTests { + + private val processor = ControllerMappingReflectiveProcessor() + + private val hints = ReflectionHints() + + @Test + fun registerReflectiveHintsForFunctionWithDefaultArgumentValue() { + val method = SampleController::class.java.getDeclaredMethod("defaultValue", Boolean::class.javaObjectType) + processor.registerReflectionHints(hints, method) + Assertions.assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( + { + Assertions.assertThat(it.type).isEqualTo(TypeReference.of(SampleController::class.java)) + Assertions.assertThat(it.methods()).extracting { executableHint: ExecutableHint -> executableHint.name } + .containsExactlyInAnyOrder("defaultValue", "defaultValue\$default") + } + ) + } + + class SampleController { + + @GetMapping("/defaultValue") + fun defaultValue(@RequestParam(required = false) argument: Boolean? = false) = argument + } + +}