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
This commit is contained in:
@@ -18,7 +18,6 @@ package org.springframework.beans;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.beans.PropertyEditor;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -42,6 +41,7 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -70,21 +70,6 @@ public abstract class BeanUtils {
|
||||
private static final Set<Class<?>> unknownEditorTypes =
|
||||
Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));
|
||||
|
||||
@Nullable
|
||||
private static final Class<?> kotlinMetadata;
|
||||
|
||||
static {
|
||||
Class<?> metadata;
|
||||
try {
|
||||
metadata = ClassUtils.forName("kotlin.Metadata", BeanUtils.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Kotlin API not available - no special support for Kotlin class instantiation
|
||||
metadata = null;
|
||||
}
|
||||
kotlinMetadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to instantiate a class using its no-arg constructor.
|
||||
@@ -127,7 +112,7 @@ public abstract class BeanUtils {
|
||||
throw new BeanInstantiationException(clazz, "Specified class is an interface");
|
||||
}
|
||||
try {
|
||||
Constructor<T> ctor = (useKotlinSupport(clazz) ?
|
||||
Constructor<T> ctor = (KotlinDetector.isKotlinType(clazz) ?
|
||||
KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
|
||||
if (ctor == null) {
|
||||
throw new BeanInstantiationException(clazz, "No default constructor found");
|
||||
@@ -174,7 +159,7 @@ public abstract class BeanUtils {
|
||||
Assert.notNull(ctor, "Constructor must not be null");
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(ctor);
|
||||
return (useKotlinSupport(ctor.getDeclaringClass()) ?
|
||||
return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
|
||||
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
|
||||
}
|
||||
catch (InstantiationException ex) {
|
||||
@@ -191,6 +176,28 @@ public abstract class BeanUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the primary constructor of the provided class. For Kotlin classes, this
|
||||
* returns the Java constructor corresponding to the Kotlin primary constructor
|
||||
* (as defined in the Kotlin specification). Otherwise, in particular for non-Kotlin
|
||||
* classes, this simply returns {@code null}.
|
||||
* @param clazz the class to check
|
||||
* @since 5.0
|
||||
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
if (KotlinDetector.isKotlinType(clazz)) {
|
||||
Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
|
||||
if (kotlinPrimaryConstructor != null) {
|
||||
return kotlinPrimaryConstructor;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method with the given method name and the given parameter types,
|
||||
* declared on the given class or one of its superclasses. Prefers public methods,
|
||||
@@ -331,40 +338,6 @@ public abstract class BeanUtils {
|
||||
return targetMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the primary constructor of the provided class. For Java classes, it returns
|
||||
* the single or the default constructor if any. For Kotlin classes, it returns the Java
|
||||
* constructor corresponding to the Kotlin primary constructor (as defined in
|
||||
* Kotlin specification), the single or the default constructor if any.
|
||||
*
|
||||
* @param clazz the class to check
|
||||
* @since 5.0
|
||||
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">Kotlin docs</a>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
Assert.notNull(clazz, "Class must not be null");
|
||||
if (useKotlinSupport(clazz)) {
|
||||
Constructor<T> kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz);
|
||||
if (kotlinPrimaryConstructor != null) {
|
||||
return kotlinPrimaryConstructor;
|
||||
}
|
||||
}
|
||||
Constructor<T>[] ctors = (Constructor<T>[]) clazz.getConstructors();
|
||||
if (ctors.length == 1) {
|
||||
return ctors[0];
|
||||
}
|
||||
else {
|
||||
try {
|
||||
return clazz.getDeclaredConstructor();
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a method signature in the form {@code methodName[([arg_list])]},
|
||||
* where {@code arg_list} is an optional, comma-separated list of fully-qualified
|
||||
@@ -712,15 +685,6 @@ public abstract class BeanUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Kotlin is present and if the specified class is a Kotlin one.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean useKotlinSupport(Class<?> clazz) {
|
||||
return (kotlinMetadata != null &&
|
||||
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||
@@ -736,13 +700,13 @@ public abstract class BeanUtils {
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
try {
|
||||
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
|
||||
if (primaryConstructor == null) {
|
||||
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
|
||||
if (primaryCtor == null) {
|
||||
return null;
|
||||
}
|
||||
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
|
||||
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
|
||||
Assert.notNull(constructor,
|
||||
() -> "Failed to find Java constructor corresponding to Kotlin primary constructor: " + clazz.getName());
|
||||
() -> "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
|
||||
return constructor;
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
@@ -765,9 +729,9 @@ public abstract class BeanUtils {
|
||||
List<KParameter> parameters = kotlinConstructor.getParameters();
|
||||
Map<KParameter, Object> argParameters = new HashMap<>(parameters.size());
|
||||
Assert.isTrue(args.length <= parameters.size(),
|
||||
"The number of provided arguments should be less of equals than the number of constructor parameters");
|
||||
"Number of provided arguments should be less of equals than number of constructor parameters");
|
||||
for (int i = 0 ; i < args.length ; i++) {
|
||||
if (!(parameters.get(i).isOptional() && (args[i] == null))) {
|
||||
if (!(parameters.get(i).isOptional() && args[i] == null)) {
|
||||
argParameters.put(parameters.get(i), args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.full.KClasses;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -123,22 +119,6 @@ import org.springframework.util.StringUtils;
|
||||
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
|
||||
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
|
||||
|
||||
@Nullable
|
||||
private static final Class<?> kotlinMetadata;
|
||||
|
||||
static {
|
||||
Class<?> metadata;
|
||||
try {
|
||||
metadata = ClassUtils.forName("kotlin.Metadata", AutowiredAnnotationBeanPostProcessor.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Kotlin API not available - no Kotlin support
|
||||
metadata = null;
|
||||
}
|
||||
kotlinMetadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>();
|
||||
@@ -303,12 +283,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
List<Constructor<?>> candidates = new ArrayList<Constructor<?>>(rawCandidates.length);
|
||||
Constructor<?> requiredConstructor = null;
|
||||
Constructor<?> defaultConstructor = null;
|
||||
Constructor<?> kotlinPrimaryConstructor = null;
|
||||
if (useKotlinSupport(beanClass)) {
|
||||
kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(beanClass);
|
||||
}
|
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
|
||||
for (Constructor<?> candidate : rawCandidates) {
|
||||
if (kotlinPrimaryConstructor != null && candidate.isSynthetic()) {
|
||||
if (primaryConstructor != null && candidate.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
|
||||
@@ -366,10 +343,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
||||
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
||||
}
|
||||
else if (kotlinPrimaryConstructor != null) {
|
||||
else if (primaryConstructor != null) {
|
||||
candidateConstructors = (defaultConstructor != null ?
|
||||
new Constructor<?>[] {kotlinPrimaryConstructor, defaultConstructor} :
|
||||
new Constructor<?>[] {kotlinPrimaryConstructor});
|
||||
new Constructor<?>[] {primaryConstructor, defaultConstructor} :
|
||||
new Constructor<?>[] {primaryConstructor});
|
||||
}
|
||||
else {
|
||||
candidateConstructors = new Constructor<?>[0];
|
||||
@@ -381,15 +358,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
return (candidateConstructors.length > 0 ? candidateConstructors : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Kotlin is present and if the specified class is a Kotlin one.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean useKotlinSupport(Class<?> clazz) {
|
||||
return (kotlinMetadata != null &&
|
||||
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyValues postProcessPropertyValues(
|
||||
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
|
||||
@@ -771,32 +739,4 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||
*/
|
||||
private static class KotlinDelegate {
|
||||
|
||||
/**
|
||||
* Return the Java constructor corresponding to the Kotlin primary constructor if any.
|
||||
* @param clazz the {@link Class} of the Kotlin class
|
||||
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">http://kotlinlang.org/docs/reference/classes.html#constructors</a>
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
try {
|
||||
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
|
||||
if (primaryConstructor == null) {
|
||||
return null;
|
||||
}
|
||||
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
|
||||
Assert.notNull(constructor, "Can't get the Java constructor corresponding to the Kotlin primary constructor of " + clazz.getName());
|
||||
return constructor;
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,11 +34,11 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Descriptor for a specific dependency that is about to be injected.
|
||||
@@ -51,22 +51,6 @@ import org.springframework.util.ClassUtils;
|
||||
@SuppressWarnings("serial")
|
||||
public class DependencyDescriptor extends InjectionPoint implements Serializable {
|
||||
|
||||
@Nullable
|
||||
private static final Class<?> kotlinMetadata;
|
||||
|
||||
static {
|
||||
Class<?> metadata;
|
||||
try {
|
||||
metadata = ClassUtils.forName("kotlin.Metadata", DependencyDescriptor.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Kotlin API not available - no Kotlin support
|
||||
metadata = null;
|
||||
}
|
||||
kotlinMetadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
private final Class<?> declaringClass;
|
||||
|
||||
@Nullable
|
||||
@@ -183,22 +167,14 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
|
||||
|
||||
if (this.field != null) {
|
||||
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
|
||||
(useKotlinSupport(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field)));
|
||||
(KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
|
||||
KotlinDelegate.isNullable(this.field)));
|
||||
}
|
||||
else {
|
||||
return !obtainMethodParameter().isOptional();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Kotlin is present and if the specified class is a Kotlin one.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean useKotlinSupport(Class<?> clazz) {
|
||||
return (kotlinMetadata != null &&
|
||||
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the underlying field is annotated with any variant of a
|
||||
* {@code Nullable} annotation, e.g. {@code javax.annotation.Nullable} or
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.beans.ConstructorProperties;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -32,11 +31,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
|
||||
import org.springframework.beans.BeanMetadataElement;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
@@ -82,22 +76,6 @@ class ConstructorResolver {
|
||||
private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
|
||||
new NamedThreadLocal<>("Current injection point");
|
||||
|
||||
@Nullable
|
||||
private static final Class<?> kotlinMetadata;
|
||||
|
||||
static {
|
||||
Class<?> metadata;
|
||||
try {
|
||||
metadata = ClassUtils.forName("kotlin.Metadata", ConstructorResolver.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Kotlin API not available - no Kotlin support
|
||||
metadata = null;
|
||||
}
|
||||
kotlinMetadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
private final AbstractAutowireCapableBeanFactory beanFactory;
|
||||
|
||||
|
||||
@@ -818,8 +796,8 @@ class ConstructorResolver {
|
||||
* Template method for resolving the specified argument which is supposed to be autowired.
|
||||
*/
|
||||
@Nullable
|
||||
protected Object resolveAutowiredArgument(
|
||||
MethodParameter param, String beanName, @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter) {
|
||||
protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
|
||||
@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter) {
|
||||
|
||||
if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) {
|
||||
InjectionPoint injectionPoint = currentInjectionPoint.get();
|
||||
@@ -828,18 +806,8 @@ class ConstructorResolver {
|
||||
}
|
||||
return injectionPoint;
|
||||
}
|
||||
boolean required = !(useKotlinSupport(param.getContainingClass()) && KotlinDelegate.isOptional(param));
|
||||
return this.beanFactory.resolveDependency(
|
||||
new DependencyDescriptor(param, required), beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Kotlin is present and if the specified class is a Kotlin one.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean useKotlinSupport(Class<?> clazz) {
|
||||
return (kotlinMetadata != null &&
|
||||
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
|
||||
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
|
||||
static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) {
|
||||
@@ -947,37 +915,4 @@ class ConstructorResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||
*/
|
||||
private static class KotlinDelegate {
|
||||
|
||||
/**
|
||||
* Check whether the specified {@link MethodParameter} represents an optional Kotlin parameter or not.
|
||||
*/
|
||||
public static boolean isOptional(MethodParameter param) {
|
||||
Method method = param.getMethod();
|
||||
Constructor<?> ctor = param.getConstructor();
|
||||
int index = param.getParameterIndex();
|
||||
KFunction<?> function = null;
|
||||
if (method != null) {
|
||||
function = ReflectJvmMapping.getKotlinFunction(method);
|
||||
}
|
||||
else if (ctor != null) {
|
||||
function = ReflectJvmMapping.getKotlinFunction(ctor);
|
||||
}
|
||||
if (function != null) {
|
||||
List<KParameter> parameters = function.getParameters();
|
||||
return parameters
|
||||
.stream()
|
||||
.filter(p -> KParameter.Kind.VALUE.equals(p.getKind()))
|
||||
.collect(Collectors.toList())
|
||||
.get(index)
|
||||
.isOptional();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user