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:
parent
d3129a8bd7
commit
ec345bf162
|
|
@ -18,7 +18,6 @@ package org.springframework.beans;
|
||||||
|
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
import java.beans.PropertyEditor;
|
import java.beans.PropertyEditor;
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
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.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.core.KotlinDetector;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -70,21 +70,6 @@ public abstract class BeanUtils {
|
||||||
private static final Set<Class<?>> unknownEditorTypes =
|
private static final Set<Class<?>> unknownEditorTypes =
|
||||||
Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));
|
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.
|
* 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");
|
throw new BeanInstantiationException(clazz, "Specified class is an interface");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Constructor<T> ctor = (useKotlinSupport(clazz) ?
|
Constructor<T> ctor = (KotlinDetector.isKotlinType(clazz) ?
|
||||||
KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
|
KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
|
||||||
if (ctor == null) {
|
if (ctor == null) {
|
||||||
throw new BeanInstantiationException(clazz, "No default constructor found");
|
throw new BeanInstantiationException(clazz, "No default constructor found");
|
||||||
|
|
@ -174,7 +159,7 @@ public abstract class BeanUtils {
|
||||||
Assert.notNull(ctor, "Constructor must not be null");
|
Assert.notNull(ctor, "Constructor must not be null");
|
||||||
try {
|
try {
|
||||||
ReflectionUtils.makeAccessible(ctor);
|
ReflectionUtils.makeAccessible(ctor);
|
||||||
return (useKotlinSupport(ctor.getDeclaringClass()) ?
|
return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
|
||||||
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
|
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
|
||||||
}
|
}
|
||||||
catch (InstantiationException ex) {
|
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,
|
* 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,
|
* declared on the given class or one of its superclasses. Prefers public methods,
|
||||||
|
|
@ -331,40 +338,6 @@ public abstract class BeanUtils {
|
||||||
return targetMethod;
|
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])]},
|
* Parse a method signature in the form {@code methodName[([arg_list])]},
|
||||||
* where {@code arg_list} is an optional, comma-separated list of fully-qualified
|
* 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.
|
* Inner class to avoid a hard dependency on Kotlin at runtime.
|
||||||
|
|
@ -736,13 +700,13 @@ public abstract class BeanUtils {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||||
try {
|
try {
|
||||||
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
|
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
|
||||||
if (primaryConstructor == null) {
|
if (primaryCtor == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
|
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
|
||||||
Assert.notNull(constructor,
|
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;
|
return constructor;
|
||||||
}
|
}
|
||||||
catch (UnsupportedOperationException ex) {
|
catch (UnsupportedOperationException ex) {
|
||||||
|
|
@ -765,9 +729,9 @@ public abstract class BeanUtils {
|
||||||
List<KParameter> parameters = kotlinConstructor.getParameters();
|
List<KParameter> parameters = kotlinConstructor.getParameters();
|
||||||
Map<KParameter, Object> argParameters = new HashMap<>(parameters.size());
|
Map<KParameter, Object> argParameters = new HashMap<>(parameters.size());
|
||||||
Assert.isTrue(args.length <= 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++) {
|
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]);
|
argParameters.put(parameters.get(i), args[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,6 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
|
@ -123,22 +119,6 @@ import org.springframework.util.StringUtils;
|
||||||
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
|
public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
|
||||||
implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
|
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());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>();
|
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);
|
List<Constructor<?>> candidates = new ArrayList<Constructor<?>>(rawCandidates.length);
|
||||||
Constructor<?> requiredConstructor = null;
|
Constructor<?> requiredConstructor = null;
|
||||||
Constructor<?> defaultConstructor = null;
|
Constructor<?> defaultConstructor = null;
|
||||||
Constructor<?> kotlinPrimaryConstructor = null;
|
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
|
||||||
if (useKotlinSupport(beanClass)) {
|
|
||||||
kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(beanClass);
|
|
||||||
}
|
|
||||||
for (Constructor<?> candidate : rawCandidates) {
|
for (Constructor<?> candidate : rawCandidates) {
|
||||||
if (kotlinPrimaryConstructor != null && candidate.isSynthetic()) {
|
if (primaryConstructor != null && candidate.isSynthetic()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
|
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
|
||||||
|
|
@ -366,10 +343,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||||
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
||||||
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
||||||
}
|
}
|
||||||
else if (kotlinPrimaryConstructor != null) {
|
else if (primaryConstructor != null) {
|
||||||
candidateConstructors = (defaultConstructor != null ?
|
candidateConstructors = (defaultConstructor != null ?
|
||||||
new Constructor<?>[] {kotlinPrimaryConstructor, defaultConstructor} :
|
new Constructor<?>[] {primaryConstructor, defaultConstructor} :
|
||||||
new Constructor<?>[] {kotlinPrimaryConstructor});
|
new Constructor<?>[] {primaryConstructor});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
candidateConstructors = new Constructor<?>[0];
|
candidateConstructors = new Constructor<?>[0];
|
||||||
|
|
@ -381,15 +358,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||||
return (candidateConstructors.length > 0 ? candidateConstructors : null);
|
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
|
@Override
|
||||||
public PropertyValues postProcessPropertyValues(
|
public PropertyValues postProcessPropertyValues(
|
||||||
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
|
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.InjectionPoint;
|
||||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||||
import org.springframework.core.GenericTypeResolver;
|
import org.springframework.core.GenericTypeResolver;
|
||||||
|
import org.springframework.core.KotlinDetector;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.ParameterNameDiscoverer;
|
import org.springframework.core.ParameterNameDiscoverer;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Descriptor for a specific dependency that is about to be injected.
|
* Descriptor for a specific dependency that is about to be injected.
|
||||||
|
|
@ -51,22 +51,6 @@ import org.springframework.util.ClassUtils;
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class DependencyDescriptor extends InjectionPoint implements Serializable {
|
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;
|
private final Class<?> declaringClass;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -183,22 +167,14 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
|
||||||
|
|
||||||
if (this.field != null) {
|
if (this.field != null) {
|
||||||
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
|
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 {
|
else {
|
||||||
return !obtainMethodParameter().isOptional();
|
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
|
* Check whether the underlying field is annotated with any variant of a
|
||||||
* {@code Nullable} annotation, e.g. {@code javax.annotation.Nullable} or
|
* {@code Nullable} annotation, e.g. {@code javax.annotation.Nullable} or
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.beans.factory.support;
|
package org.springframework.beans.factory.support;
|
||||||
|
|
||||||
import java.beans.ConstructorProperties;
|
import java.beans.ConstructorProperties;
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
@ -32,11 +31,6 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.BeanMetadataElement;
|
||||||
import org.springframework.beans.BeanWrapper;
|
import org.springframework.beans.BeanWrapper;
|
||||||
|
|
@ -82,22 +76,6 @@ class ConstructorResolver {
|
||||||
private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
|
private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint =
|
||||||
new NamedThreadLocal<>("Current injection point");
|
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;
|
private final AbstractAutowireCapableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -818,8 +796,8 @@ class ConstructorResolver {
|
||||||
* Template method for resolving the specified argument which is supposed to be autowired.
|
* Template method for resolving the specified argument which is supposed to be autowired.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
protected Object resolveAutowiredArgument(
|
protected Object resolveAutowiredArgument(MethodParameter param, String beanName,
|
||||||
MethodParameter param, String beanName, @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter) {
|
@Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter) {
|
||||||
|
|
||||||
if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) {
|
if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) {
|
||||||
InjectionPoint injectionPoint = currentInjectionPoint.get();
|
InjectionPoint injectionPoint = currentInjectionPoint.get();
|
||||||
|
|
@ -828,18 +806,8 @@ class ConstructorResolver {
|
||||||
}
|
}
|
||||||
return injectionPoint;
|
return injectionPoint;
|
||||||
}
|
}
|
||||||
boolean required = !(useKotlinSupport(param.getContainingClass()) && KotlinDelegate.isOptional(param));
|
|
||||||
return this.beanFactory.resolveDependency(
|
return this.beanFactory.resolveDependency(
|
||||||
new DependencyDescriptor(param, required), beanName, autowiredBeanNames, typeConverter);
|
new DependencyDescriptor(param, true), 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2013 the original author or authors.
|
* Copyright 2002-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -18,7 +18,6 @@ package org.springframework.beans;
|
||||||
|
|
||||||
import java.beans.Introspector;
|
import java.beans.Introspector;
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -276,23 +275,6 @@ public class BeanUtilsTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFindDefaultConstructorAndInstantiate() {
|
|
||||||
Constructor<Bean> ctor = BeanUtils.findPrimaryConstructor(Bean.class);
|
|
||||||
assertNotNull(ctor);
|
|
||||||
Bean bean = BeanUtils.instantiateClass(ctor);
|
|
||||||
assertNotNull(bean);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFindSingleNonDefaultConstructorAndInstantiate() {
|
|
||||||
Constructor<BeanWithSingleNonDefaultConstructor> ctor = BeanUtils.findPrimaryConstructor(BeanWithSingleNonDefaultConstructor.class);
|
|
||||||
assertNotNull(ctor);
|
|
||||||
BeanWithSingleNonDefaultConstructor bean = BeanUtils.instantiateClass(ctor, "foo");
|
|
||||||
assertNotNull(bean);
|
|
||||||
assertEquals("foo", bean.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertSignatureEquals(Method desiredMethod, String signature) {
|
private void assertSignatureEquals(Method desiredMethod, String signature) {
|
||||||
assertEquals(desiredMethod, BeanUtils.resolveSignature(signature, MethodSignatureBean.class));
|
assertEquals(desiredMethod, BeanUtils.resolveSignature(signature, MethodSignatureBean.class));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,26 +66,6 @@ class BeanUtilsKotlinTests {
|
||||||
assertEquals(12, baz.param2)
|
assertEquals(12, baz.param2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `2 constructors with default one`() {
|
|
||||||
assertEquals(TwoConstructorsWithDefaultOne::class.java.getDeclaredConstructor(), BeanUtils.findPrimaryConstructor(TwoConstructorsWithDefaultOne::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `2 constructors without default one`() {
|
|
||||||
assertNull(BeanUtils.findPrimaryConstructor(TwoConstructorsWithoutDefaultOne::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `1 constructor with default one`() {
|
|
||||||
assertEquals(OneConstructorWithDefaultOne::class.java.getDeclaredConstructor(), BeanUtils.findPrimaryConstructor(OneConstructorWithDefaultOne::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `1 constructor without default one`() {
|
|
||||||
assertEquals(OneConstructorWithoutDefaultOne::class.java.getDeclaredConstructor(String::class.java), BeanUtils.findPrimaryConstructor(OneConstructorWithoutDefaultOne::class.java))
|
|
||||||
}
|
|
||||||
|
|
||||||
class Foo(val param1: String, val param2: Int)
|
class Foo(val param1: String, val param2: Int)
|
||||||
|
|
||||||
class Bar(val param1: String, val param2: Int = 12)
|
class Bar(val param1: String, val param2: Int = 12)
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,8 @@ public class FieldError extends ObjectError {
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
|
return "Field error in object '" + getObjectName() + "' on field '" + this.field +
|
||||||
"': rejected value [" + this.rejectedValue + "]; " + resolvableToString();
|
"': rejected value [" + ObjectUtils.nullSafeToString(this.rejectedValue) + "]; " +
|
||||||
|
resolvableToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2017 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
|
||||||
|
*
|
||||||
|
* http://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.core;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types.
|
||||||
|
*
|
||||||
|
* @author Juergen Hoeller
|
||||||
|
* @author Sebastien Deleuze
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public abstract class KotlinDetector {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static final Class<? extends Annotation> kotlinMetadata;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Class<?> metadata;
|
||||||
|
try {
|
||||||
|
metadata = ClassUtils.forName("kotlin.Metadata", KotlinDetector.class.getClassLoader());
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException ex) {
|
||||||
|
// Kotlin API not available - no Kotlin support
|
||||||
|
metadata = null;
|
||||||
|
}
|
||||||
|
kotlinMetadata = (Class<? extends Annotation>) metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether Kotlin is present in general.
|
||||||
|
*/
|
||||||
|
public static boolean isKotlinPresent() {
|
||||||
|
return (kotlinMetadata != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the given {@code Class} is a Kotlin type
|
||||||
|
* (with Kotlin metadata present on it).
|
||||||
|
*/
|
||||||
|
public static boolean isKotlinType(Class<?> clazz) {
|
||||||
|
return (kotlinMetadata != null && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.core;
|
package org.springframework.core;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -27,7 +26,6 @@ import kotlin.reflect.KParameter;
|
||||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
|
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
|
||||||
|
|
@ -41,27 +39,13 @@ import org.springframework.util.ClassUtils;
|
||||||
*/
|
*/
|
||||||
public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
|
public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static final Class<?> kotlinMetadata;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Class<?> metadata;
|
|
||||||
try {
|
|
||||||
metadata = ClassUtils.forName("kotlin.Metadata", KotlinReflectionParameterNameDiscoverer.class.getClassLoader());
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException ex) {
|
|
||||||
// Kotlin API not available - no special support for Kotlin class instantiation
|
|
||||||
metadata = null;
|
|
||||||
}
|
|
||||||
kotlinMetadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public String[] getParameterNames(Method method) {
|
public String[] getParameterNames(Method method) {
|
||||||
if (!useKotlinSupport(method.getDeclaringClass())) {
|
if (!KotlinDetector.isKotlinType(method.getDeclaringClass())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||||
return (function != null ? getParameterNames(function.getParameters()) : null);
|
return (function != null ? getParameterNames(function.getParameters()) : null);
|
||||||
|
|
@ -74,9 +58,10 @@ public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDis
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public String[] getParameterNames(Constructor<?> ctor) {
|
public String[] getParameterNames(Constructor<?> ctor) {
|
||||||
if (!useKotlinSupport(ctor.getDeclaringClass())) {
|
if (!KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(ctor);
|
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(ctor);
|
||||||
return (function != null ? getParameterNames(function.getParameters()) : null);
|
return (function != null ? getParameterNames(function.getParameters()) : null);
|
||||||
|
|
@ -103,13 +88,4 @@ public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDis
|
||||||
return parameterNames;
|
return parameterNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||||
|
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
|
* Helper class that encapsulates the specification of a method parameter, i.e. a {@link Method}
|
||||||
|
|
@ -58,22 +57,6 @@ import org.springframework.util.ClassUtils;
|
||||||
*/
|
*/
|
||||||
public class MethodParameter {
|
public class MethodParameter {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static final Class<?> kotlinMetadata;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Class<?> metadata;
|
|
||||||
try {
|
|
||||||
metadata = ClassUtils.forName("kotlin.Metadata", MethodParameter.class.getClassLoader());
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException ex) {
|
|
||||||
// Kotlin API not available - no Kotlin support
|
|
||||||
metadata = null;
|
|
||||||
}
|
|
||||||
kotlinMetadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private final Executable executable;
|
private final Executable executable;
|
||||||
|
|
||||||
private final int parameterIndex;
|
private final int parameterIndex;
|
||||||
|
|
@ -353,16 +336,7 @@ public class MethodParameter {
|
||||||
*/
|
*/
|
||||||
public boolean isOptional() {
|
public boolean isOptional() {
|
||||||
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
|
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
|
||||||
(useKotlinSupport(this.getContainingClass()) && KotlinDelegate.isNullable(this)));
|
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this)));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -754,9 +728,10 @@ public class MethodParameter {
|
||||||
private static class KotlinDelegate {
|
private static class KotlinDelegate {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the specified {@link MethodParameter} represents a nullable Kotlin type or not.
|
* Check whether the specified {@link MethodParameter} represents a nullable Kotlin type
|
||||||
|
* or an optional parameter (with a default value in the Kotlin declaration).
|
||||||
*/
|
*/
|
||||||
public static boolean isNullable(MethodParameter param) {
|
public static boolean isOptional(MethodParameter param) {
|
||||||
Method method = param.getMethod();
|
Method method = param.getMethod();
|
||||||
Constructor<?> ctor = param.getConstructor();
|
Constructor<?> ctor = param.getConstructor();
|
||||||
int index = param.getParameterIndex();
|
int index = param.getParameterIndex();
|
||||||
|
|
@ -774,13 +749,12 @@ public class MethodParameter {
|
||||||
}
|
}
|
||||||
if (function != null) {
|
if (function != null) {
|
||||||
List<KParameter> parameters = function.getParameters();
|
List<KParameter> parameters = function.getParameters();
|
||||||
return parameters
|
KParameter parameter = parameters
|
||||||
.stream()
|
.stream()
|
||||||
.filter(p -> KParameter.Kind.VALUE.equals(p.getKind()))
|
.filter(p -> KParameter.Kind.VALUE.equals(p.getKind()))
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
.get(index)
|
.get(index);
|
||||||
.getType()
|
return (parameter.getType().isMarkedNullable() || parameter.isOptional());
|
||||||
.isMarkedNullable();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.FatalBeanException;
|
import org.springframework.beans.FatalBeanException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.KotlinDetector;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
@ -762,7 +763,7 @@ public class Jackson2ObjectMapperBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kotlin present?
|
// Kotlin present?
|
||||||
if (ClassUtils.isPresent("kotlin.Metadata", this.moduleClassLoader)) {
|
if (KotlinDetector.isKotlinPresent()) {
|
||||||
try {
|
try {
|
||||||
Class<? extends Module> kotlinModule = (Class<? extends Module>)
|
Class<? extends Module> kotlinModule = (Class<? extends Module>)
|
||||||
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
|
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
|
||||||
|
|
|
||||||
|
|
@ -198,11 +198,22 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||||
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
|
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
|
||||||
|
|
||||||
MethodParameter nestedParameter = parameter.nestedIfOptional();
|
MethodParameter nestedParameter = parameter.nestedIfOptional();
|
||||||
Class<?> type = nestedParameter.getNestedParameterType();
|
Class<?> clazz = nestedParameter.getNestedParameterType();
|
||||||
|
|
||||||
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(type);
|
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
|
||||||
if (ctor == null) {
|
if (ctor == null) {
|
||||||
throw new IllegalStateException("No primary constructor found for " + type.getName());
|
Constructor<?>[] ctors = clazz.getConstructors();
|
||||||
|
if (ctors.length == 1) {
|
||||||
|
ctor = ctors[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
ctor = clazz.getDeclaredConstructor();
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException ex) {
|
||||||
|
throw new IllegalStateException("No primary or default constructor found for " + clazz, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object attribute = constructAttribute(ctor, attributeName, binderFactory, webRequest);
|
Object attribute = constructAttribute(ctor, attributeName, binderFactory, webRequest);
|
||||||
|
|
@ -263,8 +274,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
args[i] = (value != null ?
|
MethodParameter methodParam = new MethodParameter(ctor, i);
|
||||||
binder.convertIfNecessary(value, paramType, new MethodParameter(ctor, i)) : null);
|
if (value == null && methodParam.isOptional()) {
|
||||||
|
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
args[i] = binder.convertIfNecessary(value, paramType, methodParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (TypeMismatchException ex) {
|
catch (TypeMismatchException ex) {
|
||||||
bindingFailure = true;
|
bindingFailure = true;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.MonoProcessor;
|
import reactor.core.publisher.MonoProcessor;
|
||||||
|
|
@ -196,15 +197,29 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<?> createAttribute(
|
private Mono<?> createAttribute(
|
||||||
String attributeName, Class<?> attributeType, BindingContext context, ServerWebExchange exchange) {
|
String attributeName, Class<?> clazz, BindingContext context, ServerWebExchange exchange) {
|
||||||
|
|
||||||
Constructor<?>[] ctors = attributeType.getConstructors();
|
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
|
||||||
if (ctors.length != 1) {
|
if (ctor == null) {
|
||||||
// No standard data class or standard JavaBeans arrangement ->
|
Constructor<?>[] ctors = clazz.getConstructors();
|
||||||
// defensively go with default constructor, expecting regular bean property bindings.
|
if (ctors.length == 1) {
|
||||||
return Mono.just(BeanUtils.instantiateClass(attributeType));
|
ctor = ctors[0];
|
||||||
}
|
}
|
||||||
Constructor<?> ctor = ctors[0];
|
else {
|
||||||
|
try {
|
||||||
|
ctor = clazz.getDeclaredConstructor();
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException ex) {
|
||||||
|
throw new IllegalStateException("No primary or default constructor found for " + clazz, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constructAttribute(ctor, attributeName, context, exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<?> constructAttribute(Constructor<?> ctor, String attributeName,
|
||||||
|
BindingContext context, ServerWebExchange exchange) {
|
||||||
|
|
||||||
if (ctor.getParameterCount() == 0) {
|
if (ctor.getParameterCount() == 0) {
|
||||||
// A single default constructor -> clearly a standard JavaBeans arrangement.
|
// A single default constructor -> clearly a standard JavaBeans arrangement.
|
||||||
return Mono.just(BeanUtils.instantiateClass(ctor));
|
return Mono.just(BeanUtils.instantiateClass(ctor));
|
||||||
|
|
@ -237,7 +252,13 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value = (value instanceof List ? ((List<?>) value).toArray() : value);
|
value = (value instanceof List ? ((List<?>) value).toArray() : value);
|
||||||
args[i] = binder.convertIfNecessary(value, paramTypes[i], new MethodParameter(ctor, i));
|
MethodParameter methodParam = new MethodParameter(ctor, i);
|
||||||
|
if (value == null && methodParam.isOptional()) {
|
||||||
|
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
args[i] = binder.convertIfNecessary(value, paramTypes[i], methodParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BeanUtils.instantiateClass(ctor, args);
|
return BeanUtils.instantiateClass(ctor, args);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||||
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
|
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
@ -99,6 +100,7 @@ import org.springframework.tests.sample.beans.TestBean;
|
||||||
import org.springframework.ui.ExtendedModelMap;
|
import org.springframework.ui.ExtendedModelMap;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.SerializationTestUtils;
|
import org.springframework.util.SerializationTestUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
@ -1761,6 +1763,30 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
assertEquals("value1-true-3", response.getContentAsString());
|
assertEquals("value1-true-3", response.getContentAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dataClassBindingWithOptionalParameter() throws Exception {
|
||||||
|
initServletWithControllers(ValidatedDataClassController.class);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
request.addParameter("param2", "true");
|
||||||
|
request.addParameter("optionalParam", "8");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
getServlet().service(request, response);
|
||||||
|
assertEquals("value1-true-8", response.getContentAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dataClassBindingWithMissingParameter() throws Exception {
|
||||||
|
initServletWithControllers(ValidatedDataClassController.class);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
getServlet().service(request, response);
|
||||||
|
assertTrue(response.getContentAsString().contains("field 'param2'"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataClassBindingWithConversionError() throws Exception {
|
public void dataClassBindingWithConversionError() throws Exception {
|
||||||
initServletWithControllers(ValidatedDataClassController.class);
|
initServletWithControllers(ValidatedDataClassController.class);
|
||||||
|
|
@ -3390,10 +3416,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
|
|
||||||
public int param3;
|
public int param3;
|
||||||
|
|
||||||
@ConstructorProperties({"param1", "param2"})
|
@ConstructorProperties({"param1", "param2", "optionalParam"})
|
||||||
public DataClass(String param1, boolean p2) {
|
public DataClass(String param1, boolean p2, Optional<Integer> optionalParam) {
|
||||||
this.param1 = param1;
|
this.param1 = param1;
|
||||||
this.param2 = p2;
|
this.param2 = p2;
|
||||||
|
Assert.notNull(optionalParam, "Optional must not be null");
|
||||||
|
optionalParam.ifPresent(integer -> this.param3 = integer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParam3(int param3) {
|
public void setParam3(int param3) {
|
||||||
|
|
@ -3418,6 +3446,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
LocalValidatorFactoryBean vf = new LocalValidatorFactoryBean();
|
LocalValidatorFactoryBean vf = new LocalValidatorFactoryBean();
|
||||||
vf.afterPropertiesSet();
|
vf.afterPropertiesSet();
|
||||||
binder.setValidator(vf);
|
binder.setValidator(vf);
|
||||||
|
binder.setConversionService(new DefaultFormattingConversionService());
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/bind")
|
@RequestMapping("/bind")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue