Add support for Kotlin autowired ctors w/ optional params
This commit adds support for autowired constructor parameters on Kotlin classes with optional parameters. If some constructor parameters are not available, optional parameter default values will be used instead. Both explicit @Autowired annotated constructor and implicit single constructor automatically autowired are supported. Issue: SPR-15847
This commit is contained in:
@@ -34,6 +34,10 @@ 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;
|
||||
|
||||
@@ -110,6 +114,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Fisher
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
* @since 2.5
|
||||
* @see #setAutowiredAnnotationType
|
||||
* @see Autowired
|
||||
@@ -118,6 +123,22 @@ 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<>();
|
||||
@@ -282,7 +303,14 @@ 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);
|
||||
}
|
||||
for (Constructor<?> candidate : rawCandidates) {
|
||||
if (kotlinPrimaryConstructor != null && candidate.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
AnnotationAttributes ann = findAutowiredAnnotation(candidate);
|
||||
if (ann == null) {
|
||||
Class<?> userClass = ClassUtils.getUserClass(beanClass);
|
||||
@@ -338,6 +366,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
||||
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
||||
}
|
||||
else if (kotlinPrimaryConstructor != null) {
|
||||
candidateConstructors = new Constructor<?>[] {kotlinPrimaryConstructor};
|
||||
}
|
||||
else {
|
||||
candidateConstructors = new Constructor<?>[0];
|
||||
}
|
||||
@@ -348,6 +379,15 @@ 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 {
|
||||
@@ -729,4 +769,27 @@ 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
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;
|
||||
@@ -31,6 +32,11 @@ 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;
|
||||
@@ -65,6 +71,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Rob Harrop
|
||||
* @author Mark Fisher
|
||||
* @author Costin Leau
|
||||
* @author Sebastien Deleuze
|
||||
* @since 2.0
|
||||
* @see #autowireConstructor
|
||||
* @see #instantiateUsingFactoryMethod
|
||||
@@ -75,6 +82,22 @@ 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;
|
||||
|
||||
|
||||
@@ -805,10 +828,19 @@ class ConstructorResolver {
|
||||
}
|
||||
return injectionPoint;
|
||||
}
|
||||
boolean required = !(useKotlinSupport(param.getContainingClass()) && KotlinDelegate.isOptional(param));
|
||||
return this.beanFactory.resolveDependency(
|
||||
new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter);
|
||||
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);
|
||||
}
|
||||
|
||||
static InjectionPoint setCurrentInjectionPoint(@Nullable InjectionPoint injectionPoint) {
|
||||
InjectionPoint old = currentInjectionPoint.get();
|
||||
@@ -915,4 +947,37 @@ 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