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:
Sebastien Deleuze
2017-08-22 16:46:43 +02:00
parent ab6430569d
commit ef68ccdbd8
3 changed files with 225 additions and 13 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}