diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index f4ca77b3e1c..d0705b88564 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import kotlin.jvm.JvmClassMappingKt; +import kotlin.jvm.internal.DefaultConstructorMarker; import kotlin.reflect.KClass; import kotlin.reflect.KFunction; import kotlin.reflect.KParameter; @@ -669,7 +670,9 @@ public abstract class BeanUtils { ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class); String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor)); Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor); - Assert.state(paramNames.length == ctor.getParameterCount(), + int parameterCount = (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasDefaultConstructorMarker(ctor) ? + ctor.getParameterCount() - 1 : ctor.getParameterCount()); + Assert.state(paramNames.length == parameterCount, () -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor); return paramNames; } @@ -939,6 +942,11 @@ public abstract class BeanUtils { } return kotlinConstructor.callBy(argParameters); } + + public static boolean hasDefaultConstructorMarker(Constructor ctor) { + int parameterCount = ctor.getParameterCount(); + return parameterCount > 0 && ctor.getParameters()[parameterCount -1].getType() == DefaultConstructorMarker.class; + } } } diff --git a/spring-beans/src/test/kotlin/org/springframework/beans/BeanUtilsKotlinTests.kt b/spring-beans/src/test/kotlin/org/springframework/beans/BeanUtilsKotlinTests.kt index 5b8378a4953..1ae3b034107 100644 --- a/spring-beans/src/test/kotlin/org/springframework/beans/BeanUtilsKotlinTests.kt +++ b/spring-beans/src/test/kotlin/org/springframework/beans/BeanUtilsKotlinTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 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. @@ -157,6 +157,48 @@ class BeanUtilsKotlinTests { assertThat(instance).isEqualTo(ConstructorWithNullablePrimitiveValueClass(null)) } + @Test + fun `Get parameter names with Foo`() { + val ctor = BeanUtils.findPrimaryConstructor(Foo::class.java)!! + val names = BeanUtils.getParameterNames(ctor) + assertThat(names).containsExactly("param1", "param2") + } + + @Test + fun `Get parameter names filters out DefaultConstructorMarker with ConstructorWithValueClass`() { + val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithValueClass::class.java)!! + val names = BeanUtils.getParameterNames(ctor) + assertThat(names).containsExactly("value") + } + + @Test + fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithNullableValueClass`() { + val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithNullableValueClass::class.java)!! + val names = BeanUtils.getParameterNames(ctor) + assertThat(names).containsExactly("value") + } + + @Test + fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithPrimitiveValueClass`() { + val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithPrimitiveValueClass::class.java)!! + val names = BeanUtils.getParameterNames(ctor) + assertThat(names).containsExactly("value") + } + + @Test + fun `getParameterNames filters out DefaultConstructorMarker with ConstructorWithNullablePrimitiveValueClass`() { + val ctor = BeanUtils.findPrimaryConstructor(ConstructorWithNullablePrimitiveValueClass::class.java)!! + val names = BeanUtils.getParameterNames(ctor) + assertThat(names).containsExactly("value") + } + + @Test + fun `getParameterNames with ClassWithZeroParameterCtor`() { + val ctor = BeanUtils.findPrimaryConstructor(ClassWithZeroParameterCtor::class.java)!! + val names = BeanUtils.getParameterNames(ctor) + assertThat(names).isEmpty() + } + class Foo(val param1: String, val param2: Int) @@ -216,4 +258,6 @@ class BeanUtilsKotlinTests { data class ConstructorWithNullablePrimitiveValueClass(val value: PrimitiveValueClass?) + class ClassWithZeroParameterCtor() + }