From f8f8d28f08a118a072448271130b2ee02f72a282 Mon Sep 17 00:00:00 2001 From: Sebastien Deleuze Date: Fri, 20 Jul 2018 14:24:04 +0200 Subject: [PATCH] Support running Kotlin apps without kotlin-reflect This commit includes an optimization of BeansUtils#instantiateClass that favors Java reflection for default constructors before leveraging Kotlin one for finding primary constructors and avoids Kotlin related conditions when running in Java. Issue: SPR-17069 --- .../org/springframework/beans/BeanUtils.java | 30 ++++++------------- .../factory/config/DependencyDescriptor.java | 3 +- .../core/DefaultParameterNameDiscoverer.java | 14 ++++----- .../springframework/core/KotlinDetector.java | 24 +++++++++++++-- .../springframework/core/MethodParameter.java | 4 ++- 5 files changed, 41 insertions(+), 34 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index 886811e9b12..0cb8d53f856 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -119,11 +119,15 @@ public abstract class BeanUtils { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } try { - Constructor ctor = (KotlinDetector.isKotlinType(clazz) ? - KotlinDelegate.getPrimaryConstructor(clazz) : clazz.getDeclaredConstructor()); - return instantiateClass(ctor); + return instantiateClass(clazz.getDeclaredConstructor()); } catch (NoSuchMethodException ex) { + if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) { + Constructor ctor = findPrimaryConstructor(clazz); + if (ctor != null) { + return instantiateClass(ctor); + } + } throw new BeanInstantiationException(clazz, "No default constructor found", ex); } catch (LinkageError err) { @@ -166,7 +170,7 @@ public abstract class BeanUtils { Assert.notNull(ctor, "Constructor must not be null"); try { ReflectionUtils.makeAccessible(ctor); - return (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ? + return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ? KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args)); } catch (InstantiationException ex) { @@ -196,7 +200,7 @@ public abstract class BeanUtils { @Nullable public static Constructor findPrimaryConstructor(Class clazz) { Assert.notNull(clazz, "Class must not be null"); - if (KotlinDetector.isKotlinType(clazz)) { + if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) { Constructor kotlinPrimaryConstructor = KotlinDelegate.findPrimaryConstructor(clazz); if (kotlinPrimaryConstructor != null) { return kotlinPrimaryConstructor; @@ -700,22 +704,6 @@ public abstract class BeanUtils { */ private static class KotlinDelegate { - /** - * Determine the Java constructor corresponding to the Kotlin primary constructor. - * @param clazz the {@link Class} of the Kotlin class - * @throws NoSuchMethodException if no such constructor found - * @since 5.0.3 - * @see #findPrimaryConstructor - * @see Class#getDeclaredConstructor - */ - public static Constructor getPrimaryConstructor(Class clazz) throws NoSuchMethodException { - Constructor ctor = findPrimaryConstructor(clazz); - if (ctor == null) { - throw new NoSuchMethodException(); - } - return ctor; - } - /** * Retrieve the Java constructor corresponding to the Kotlin primary constructor, if any. * @param clazz the {@link Class} of the Kotlin class diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 146f781077e..32cf7843d84 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -168,7 +168,8 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable if (this.field != null) { return !(this.field.getType() == Optional.class || hasNullableAnnotation() || - (KotlinDetector.isKotlinType(this.field.getDeclaringClass()) && + (KotlinDetector.isKotlinReflectPresent() && + KotlinDetector.isKotlinType(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field))); } else { diff --git a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java index 2e965097a56..6c3aa7b3321 100644 --- a/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java @@ -16,17 +16,16 @@ package org.springframework.core; -import org.springframework.util.ClassUtils; - /** * Default implementation of the {@link ParameterNameDiscoverer} strategy interface, * using the Java 8 standard reflection mechanism (if available), and falling back * to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking * debug information in the class file. * - *

If Kotlin is present, {@link KotlinReflectionParameterNameDiscoverer} is added first - * in the list and used for Kotlin classes and interfaces. When compiling or running as - * a Graal native image, no {@link ParameterNameDiscoverer} is used. + *

If a Kotlin reflection implementation is present, + * {@link KotlinReflectionParameterNameDiscoverer} is added first in the list and used + * for Kotlin classes and interfaces. When compiling or running as a Graal native image, + * no {@link ParameterNameDiscoverer} is used. * *

Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}. * @@ -39,16 +38,13 @@ import org.springframework.util.ClassUtils; */ public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { - private static final boolean kotlinPresent = - ClassUtils.isPresent("kotlin.Unit", DefaultParameterNameDiscoverer.class.getClassLoader()); - // See https://github.com/oracle/graal/blob/master/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/ImageInfo.java private static final boolean inImageCode = (System.getProperty("org.graalvm.nativeimage.imagecode") != null); public DefaultParameterNameDiscoverer() { if (!inImageCode) { - if (kotlinPresent) { + if (KotlinDetector.isKotlinReflectPresent()) { addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); } addDiscoverer(new StandardReflectionParameterNameDiscoverer()); diff --git a/spring-core/src/main/java/org/springframework/core/KotlinDetector.java b/spring-core/src/main/java/org/springframework/core/KotlinDetector.java index 1cc03edddf0..733e4137dd0 100644 --- a/spring-core/src/main/java/org/springframework/core/KotlinDetector.java +++ b/spring-core/src/main/java/org/springframework/core/KotlinDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -18,6 +18,9 @@ package org.springframework.core; import java.lang.annotation.Annotation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; @@ -31,19 +34,28 @@ import org.springframework.util.ClassUtils; @SuppressWarnings("unchecked") public abstract class KotlinDetector { + private static final Log logger = LogFactory.getLog(KotlinDetector.class); + @Nullable private static final Class kotlinMetadata; + private static final boolean kotlinReflectPresent; + static { Class metadata; + ClassLoader classLoader = KotlinDetector.class.getClassLoader(); try { - metadata = ClassUtils.forName("kotlin.Metadata", KotlinDetector.class.getClassLoader()); + metadata = ClassUtils.forName("kotlin.Metadata", classLoader); } catch (ClassNotFoundException ex) { // Kotlin API not available - no Kotlin support metadata = null; } kotlinMetadata = (Class) metadata; + kotlinReflectPresent = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader); + if (kotlinMetadata != null && !kotlinReflectPresent) { + logger.info("Kotlin reflection implementation not found at runtime, related features won't be available."); + } } @@ -54,6 +66,14 @@ public abstract class KotlinDetector { return (kotlinMetadata != null); } + /** + * Determine whether Kotlin reflection is present. + * @since 5.1 + */ + public static boolean isKotlinReflectPresent() { + return kotlinReflectPresent; + } + /** * Determine whether the given {@code Class} is a Kotlin type * (with Kotlin metadata present on it). diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 11da024e6c5..75d91f07e98 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -342,7 +342,9 @@ public class MethodParameter { */ public boolean isOptional() { return (getParameterType() == Optional.class || hasNullableAnnotation() || - (KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this))); + (KotlinDetector.isKotlinReflectPresent() && + KotlinDetector.isKotlinType(getContainingClass()) && + KotlinDelegate.isOptional(this))); } /**