Skip to content

Commit ec345bf

Browse files
committed
Revised handling of missing data class arguments
Includes unified detection of Kotlin's optional parameters in MethodParameter.isOptional(), reduces BeanUtils.findPrimaryConstructor to Kotlin semantics (for reuse in AutowiredAnnotationBeanPostProcessor), and finally introduces a common KotlinDetector delegate with an isKotlinType(Class) check. Issue: SPR-15877 Issue: SPR-16020
1 parent d3129a8 commit ec345bf

File tree

14 files changed

+204
-344
lines changed

14 files changed

+204
-344
lines changed

spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

Lines changed: 31 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.beans.PropertyDescriptor;
2020
import java.beans.PropertyEditor;
21-
import java.lang.annotation.Annotation;
2221
import java.lang.reflect.Constructor;
2322
import java.lang.reflect.InvocationTargetException;
2423
import java.lang.reflect.Method;
@@ -42,6 +41,7 @@
4241
import org.apache.commons.logging.Log;
4342
import org.apache.commons.logging.LogFactory;
4443

44+
import org.springframework.core.KotlinDetector;
4545
import org.springframework.core.MethodParameter;
4646
import org.springframework.lang.Nullable;
4747
import org.springframework.util.Assert;
@@ -70,21 +70,6 @@ public abstract class BeanUtils {
7070
private static final Set<Class<?>> unknownEditorTypes =
7171
Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));
7272

73-
@Nullable
74-
private static final Class<?> kotlinMetadata;
75-
76-
static {
77-
Class<?> metadata;
78-
try {
79-
metadata = ClassUtils.forName("kotlin.Metadata", BeanUtils.class.getClassLoader());
80-
}
81-
catch (ClassNotFoundException ex) {
82-
// Kotlin API not available - no special support for Kotlin class instantiation
83-
metadata = null;
84-
}
85-
kotlinMetadata = metadata;
86-
}
87-
8873

8974
/**
9075
* Convenience method to instantiate a class using its no-arg constructor.
@@ -127,7 +112,7 @@ public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationExc
127112
throw new BeanInstantiationException(clazz, "Specified class is an interface");
128113
}
129114
try {
130-
Constructor<T> ctor = (useKotlinSupport(clazz) ?
115+
Constructor<T> ctor = (KotlinDetector.isKotlinType(clazz) ?
131116
KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
132117
if (ctor == null) {
133118
throw new BeanInstantiationException(clazz, "No default constructor found");
@@ -174,7 +159,7 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws
174159
Assert.notNull(ctor, "Constructor must not be null");
175160
try {
176161
ReflectionUtils.makeAccessible(ctor);
177-
return (useKotlinSupport(ctor.getDeclaringClass()) ?
162+
return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
178163
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
179164
}
180165
catch (InstantiationException ex) {
@@ -191,6 +176,28 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws
191176
}
192177
}
193178

179+
/**
180+
* Return the primary constructor of the provided class. For Kotlin classes, this
181+
* returns the Java constructor corresponding to the Kotlin primary constructor
182+
* (as defined in the Kotlin specification). Otherwise, in particular for non-Kotlin
183+
* classes, this simply returns {@code null}.
184+
* @param clazz the class to check
185+
* @since 5.0
186+
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
187+
*/
188+
@SuppressWarnings("unchecked")
189+
@Nullable
190+
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
191+
Assert.notNull(clazz, "Class must not be null");
192+
if (KotlinDetector.isKotlinType(clazz)) {
193+
Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
194+
if (kotlinPrimaryConstructor != null) {
195+
return kotlinPrimaryConstructor;
196+
}
197+
}
198+
return null;
199+
}
200+
194201
/**
195202
* Find a method with the given method name and the given parameter types,
196203
* declared on the given class or one of its superclasses. Prefers public methods,
@@ -331,40 +338,6 @@ else if (!method.isBridge() && targetMethod.getParameterCount() == numParams) {
331338
return targetMethod;
332339
}
333340

334-
/**
335-
* Return the primary constructor of the provided class. For Java classes, it returns
336-
* the single or the default constructor if any. For Kotlin classes, it returns the Java
337-
* constructor corresponding to the Kotlin primary constructor (as defined in
338-
* Kotlin specification), the single or the default constructor if any.
339-
*
340-
* @param clazz the class to check
341-
* @since 5.0
342-
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
343-
*/
344-
@SuppressWarnings("unchecked")
345-
@Nullable
346-
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
347-
Assert.notNull(clazz, "Class must not be null");
348-
if (useKotlinSupport(clazz)) {
349-
Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
350-
if (kotlinPrimaryConstructor != null) {
351-
return kotlinPrimaryConstructor;
352-
}
353-
}
354-
Constructor<T>[] ctors = (Constructor<T>[]) clazz.getConstructors();
355-
if (ctors.length == 1) {
356-
return ctors[0];
357-
}
358-
else {
359-
try {
360-
return clazz.getDeclaredConstructor();
361-
}
362-
catch (NoSuchMethodException ex) {
363-
return null;
364-
}
365-
}
366-
}
367-
368341
/**
369342
* Parse a method signature in the form {@code methodName[([arg_list])]},
370343
* where {@code arg_list} is an optional, comma-separated list of fully-qualified
@@ -712,15 +685,6 @@ private static void copyProperties(Object source, Object target, @Nullable Class
712685
}
713686
}
714687

715-
/**
716-
* Return true if Kotlin is present and if the specified class is a Kotlin one.
717-
*/
718-
@SuppressWarnings("unchecked")
719-
private static boolean useKotlinSupport(Class<?> clazz) {
720-
return (kotlinMetadata != null &&
721-
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
722-
}
723-
724688

725689
/**
726690
* Inner class to avoid a hard dependency on Kotlin at runtime.
@@ -736,13 +700,13 @@ private static class KotlinDelegate {
736700
@Nullable
737701
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
738702
try {
739-
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
740-
if (primaryConstructor == null) {
703+
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
704+
if (primaryCtor == null) {
741705
return null;
742706
}
743-
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
707+
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
744708
Assert.notNull(constructor,
745-
() -> "Failed to find Java constructor corresponding to Kotlin primary constructor: " + clazz.getName());
709+
() -> "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
746710
return constructor;
747711
}
748712
catch (UnsupportedOperationException ex) {
@@ -765,9 +729,9 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
765729
List<KParameter> parameters = kotlinConstructor.getParameters();
766730
Map<KParameter, Object> argParameters = new HashMap<>(parameters.size());
767731
Assert.isTrue(args.length <= parameters.size(),
768-
"The number of provided arguments should be less of equals than the number of constructor parameters");
732+
"Number of provided arguments should be less of equals than number of constructor parameters");
769733
for (int i = 0 ; i < args.length ; i++) {
770-
if (!(parameters.get(i).isOptional() && (args[i] == null))) {
734+
if (!(parameters.get(i).isOptional() && args[i] == null)) {
771735
argParameters.put(parameters.get(i), args[i]);
772736
}
773737
}

spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@
3434
import java.util.Set;
3535
import java.util.concurrent.ConcurrentHashMap;
3636

37-
import kotlin.jvm.JvmClassMappingKt;
38-
import kotlin.reflect.KFunction;
39-
import kotlin.reflect.full.KClasses;
40-
import kotlin.reflect.jvm.ReflectJvmMapping;
4137
import org.apache.commons.logging.Log;
4238
import org.apache.commons.logging.LogFactory;
4339

@@ -123,22 +119,6 @@
123119
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
124120
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
125121

126-
@Nullable
127-
private static final Class<?> kotlinMetadata;
128-
129-
static {
130-
Class<?> metadata;
131-
try {
132-
metadata = ClassUtils.forName("kotlin.Metadata", AutowiredAnnotationBeanPostProcessor.class.getClassLoader());
133-
}
134-
catch (ClassNotFoundException ex) {
135-
// Kotlin API not available - no Kotlin support
136-
metadata = null;
137-
}
138-
kotlinMetadata = metadata;
139-
}
140-
141-
142122
protected final Log logger = LogFactory.getLog(getClass());
143123

144124
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>();
@@ -303,12 +283,9 @@ public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final
303283
List<Constructor<?>> candidates = new ArrayList<Constructor<?>>(rawCandidates.length);
304284
Constructor<?> requiredConstructor = null;
305285
Constructor<?> defaultConstructor = null;
306-
Constructor<?> kotlinPrimaryConstructor = null;
307-
if (useKotlinSupport(beanClass)) {
308-
kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(beanClass);
309-
}
286+
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
310287
for (Constructor<?> candidate : rawCandidates) {
311-
if (kotlinPrimaryConstructor != null && candidate.isSynthetic()) {
288+
if (primaryConstructor != null && candidate.isSynthetic()) {
312289
continue;
313290
}
314291
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
@@ -366,10 +343,10 @@ else if (candidates.size() == 1 && logger.isWarnEnabled()) {
366343
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
367344
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
368345
}
369-
else if (kotlinPrimaryConstructor != null) {
346+
else if (primaryConstructor != null) {
370347
candidateConstructors = (defaultConstructor != null ?
371-
new Constructor<?>[] {kotlinPrimaryConstructor, defaultConstructor} :
372-
new Constructor<?>[] {kotlinPrimaryConstructor});
348+
new Constructor<?>[] {primaryConstructor, defaultConstructor} :
349+
new Constructor<?>[] {primaryConstructor});
373350
}
374351
else {
375352
candidateConstructors = new Constructor<?>[0];
@@ -381,15 +358,6 @@ else if (kotlinPrimaryConstructor != null) {
381358
return (candidateConstructors.length > 0 ? candidateConstructors : null);
382359
}
383360

384-
/**
385-
* Return true if Kotlin is present and if the specified class is a Kotlin one.
386-
*/
387-
@SuppressWarnings("unchecked")
388-
private static boolean useKotlinSupport(Class<?> clazz) {
389-
return (kotlinMetadata != null &&
390-
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
391-
}
392-
393361
@Override
394362
public PropertyValues postProcessPropertyValues(
395363
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
@@ -771,32 +739,4 @@ public Object resolveShortcut(BeanFactory beanFactory) {
771739
}
772740
}
773741

774-
/**
775-
* Inner class to avoid a hard dependency on Kotlin at runtime.
776-
*/
777-
private static class KotlinDelegate {
778-
779-
/**
780-
* Return the Java constructor corresponding to the Kotlin primary constructor if any.
781-
* @param clazz the {@link Class} of the Kotlin class
782-
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">http://kotlinlang.org/docs/reference/classes.html#constructors</a>
783-
*/
784-
@Nullable
785-
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
786-
try {
787-
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
788-
if (primaryConstructor == null) {
789-
return null;
790-
}
791-
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
792-
Assert.notNull(constructor, "Can't get the Java constructor corresponding to the Kotlin primary constructor of " + clazz.getName());
793-
return constructor;
794-
}
795-
catch (UnsupportedOperationException ex) {
796-
return null;
797-
}
798-
}
799-
800-
}
801-
802742
}

spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
import org.springframework.beans.factory.InjectionPoint;
3535
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3636
import org.springframework.core.GenericTypeResolver;
37+
import org.springframework.core.KotlinDetector;
3738
import org.springframework.core.MethodParameter;
3839
import org.springframework.core.ParameterNameDiscoverer;
3940
import org.springframework.core.ResolvableType;
4041
import org.springframework.lang.Nullable;
41-
import org.springframework.util.ClassUtils;
4242

4343
/**
4444
* Descriptor for a specific dependency that is about to be injected.
@@ -51,22 +51,6 @@
5151
@SuppressWarnings("serial")
5252
public class DependencyDescriptor extends InjectionPoint implements Serializable {
5353

54-
@Nullable
55-
private static final Class<?> kotlinMetadata;
56-
57-
static {
58-
Class<?> metadata;
59-
try {
60-
metadata = ClassUtils.forName("kotlin.Metadata", DependencyDescriptor.class.getClassLoader());
61-
}
62-
catch (ClassNotFoundException ex) {
63-
// Kotlin API not available - no Kotlin support
64-
metadata = null;
65-
}
66-
kotlinMetadata = metadata;
67-
}
68-
69-
7054
private final Class<?> declaringClass;
7155

7256
@Nullable
@@ -183,22 +167,14 @@ public boolean isRequired() {
183167

184168
if (this.field != null) {
185169
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
186-
(useKotlinSupport(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field)));
170+
(KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
171+
KotlinDelegate.isNullable(this.field)));
187172
}
188173
else {
189174
return !obtainMethodParameter().isOptional();
190175
}
191176
}
192177

193-
/**
194-
* Return true if Kotlin is present and if the specified class is a Kotlin one.
195-
*/
196-
@SuppressWarnings("unchecked")
197-
private static boolean useKotlinSupport(Class<?> clazz) {
198-
return (kotlinMetadata != null &&
199-
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
200-
}
201-
202178
/**
203179
* Check whether the underlying field is annotated with any variant of a
204180
* {@code Nullable} annotation, e.g. {@code javax.annotation.Nullable} or

0 commit comments

Comments
 (0)