Skip to content

Commit 3991ab4

Browse files
committed
Efficient Kotlin metadata detection
Issue: SPR-15673
1 parent f6e7fef commit 3991ab4

File tree

4 files changed

+33
-22
lines changed

4 files changed

+33
-22
lines changed

spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,19 @@ public abstract class BeanUtils {
7070
private static final Set<Class<?>> unknownEditorTypes =
7171
Collections.newSetFromMap(new ConcurrentReferenceHashMap<>(64));
7272

73-
private static final boolean kotlinPresent =
74-
ClassUtils.isPresent("kotlin.Unit", BeanUtils.class.getClassLoader());
75-
73+
@Nullable
74+
private static Class<?> kotlinMetadata;
75+
76+
static {
77+
try {
78+
kotlinMetadata = ClassUtils.forName("kotlin.Metadata", BeanUtils.class.getClassLoader());
79+
}
80+
catch (ClassNotFoundException ex) {
81+
// Kotlin API not available - no special support for Kotlin class instantiation
82+
kotlinMetadata = null;
83+
}
84+
}
85+
7686

7787
/**
7888
* Convenience method to instantiate a class using its no-arg constructor.
@@ -115,7 +125,7 @@ public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationExc
115125
throw new BeanInstantiationException(clazz, "Specified class is an interface");
116126
}
117127
try {
118-
Constructor<T> ctor = (kotlinPresent && isKotlinClass(clazz) ?
128+
Constructor<T> ctor = (isKotlinClass(clazz) ?
119129
KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
120130
if (ctor == null) {
121131
throw new BeanInstantiationException(clazz, "No default constructor found");
@@ -152,8 +162,8 @@ public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) thro
152162
* non-accessible (that is, non-public) constructor, and supports Kotlin classes
153163
* with optional parameters and default values.
154164
* @param ctor the constructor to instantiate
155-
* @param args the constructor arguments to apply (use null for unspecified parameter
156-
* if needed for Kotlin classes with optional parameters and default values)
165+
* @param args the constructor arguments to apply (use {@code null} for an unspecified
166+
* parameter if needed for Kotlin classes with optional parameters and default values)
157167
* @return the new instance
158168
* @throws BeanInstantiationException if the bean cannot be instantiated
159169
* @see Constructor#newInstance
@@ -162,7 +172,8 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws
162172
Assert.notNull(ctor, "Constructor must not be null");
163173
try {
164174
ReflectionUtils.makeAccessible(ctor);
165-
return (kotlinPresent && isKotlinClass(ctor.getDeclaringClass()) ? KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
175+
return (isKotlinClass(ctor.getDeclaringClass()) ?
176+
KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
166177
}
167178
catch (InstantiationException ex) {
168179
throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
@@ -329,8 +340,7 @@ else if (!method.isBridge() && targetMethod.getParameterCount() == numParams) {
329340
@Nullable
330341
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
331342
Assert.notNull(clazz, "Class must not be null");
332-
Constructor<T> ctor = null;
333-
if (kotlinPresent && isKotlinClass(clazz)) {
343+
if (isKotlinClass(clazz)) {
334344
return KotlinDelegate.findPrimaryConstructor(clazz);
335345
}
336346
else {
@@ -699,13 +709,10 @@ private static void copyProperties(Object source, Object target, @Nullable Class
699709
/**
700710
* Return true if the specified class is a Kotlin one.
701711
*/
712+
@SuppressWarnings("unchecked")
702713
private static boolean isKotlinClass(Class<?> clazz) {
703-
for (Annotation annotation : clazz.getDeclaredAnnotations()) {
704-
if (annotation.annotationType().getName().equals("kotlin.Metadata")) {
705-
return true;
706-
}
707-
}
708-
return false;
714+
return (kotlinMetadata != null &&
715+
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
709716
}
710717

711718

@@ -717,7 +724,8 @@ private static class KotlinDelegate {
717724
/**
718725
* Return the Java constructor corresponding to the Kotlin primary constructor if any.
719726
* @param clazz the {@link Class} of the Kotlin class
720-
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">http://kotlinlang.org/docs/reference/classes.html#constructors</a>
727+
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">
728+
* http://kotlinlang.org/docs/reference/classes.html#constructors</a>
721729
*/
722730
@Nullable
723731
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
@@ -726,7 +734,8 @@ public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
726734
return null;
727735
}
728736
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
729-
Assert.notNull(constructor, "Can't get the Java constructor corresponding to the Kotlin primary constructor of " + clazz.getName());
737+
Assert.notNull(constructor,
738+
() -> "Failed to find Java constructor corresponding to Kotlin primary constructor: " + clazz.getName());
730739
return constructor;
731740
}
732741

@@ -735,14 +744,16 @@ public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
735744
* @param ctor the constructor of the Kotlin class to instantiate
736745
* @param args the constructor arguments to apply (use null for unspecified parameter if needed)
737746
*/
738-
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException {
747+
public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
748+
throws IllegalAccessException, InvocationTargetException, InstantiationException {
749+
739750
KFunction<T> kotlinConstructor = ReflectJvmMapping.getKotlinFunction(ctor);
740751
if (kotlinConstructor == null) {
741752
return ctor.newInstance(args);
742753
}
743754
List<KParameter> parameters = kotlinConstructor.getParameters();
744755
Map<KParameter, Object> argParameters = new HashMap<>(parameters.size());
745-
Assert.isTrue(args.length <= parameters.size(),
756+
Assert.isTrue(args.length <= parameters.size(),
746757
"The number of provided arguments should be less of equals than the number of constructor parameters");
747758
for (int i = 0 ; i < args.length ; i++) {
748759
if (!(parameters.get(i).isOptional() && (args[i] == null))) {

spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
public class DependencyDescriptor extends InjectionPoint implements Serializable {
5454

5555
private static final boolean kotlinPresent =
56-
ClassUtils.isPresent("kotlin.Unit", DependencyDescriptor.class.getClassLoader());
56+
ClassUtils.isPresent("kotlin.Metadata", DependencyDescriptor.class.getClassLoader());
5757

5858

5959
private final Class<?> declaringClass;

spring-core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
public class MethodParameter {
6060

6161
private static final boolean kotlinPresent =
62-
ClassUtils.isPresent("kotlin.Unit", MethodParameter.class.getClassLoader());
62+
ClassUtils.isPresent("kotlin.Metadata", MethodParameter.class.getClassLoader());
6363

6464

6565
private final Executable executable;

spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
772772
}
773773

774774
// Kotlin present?
775-
if (ClassUtils.isPresent("kotlin.Unit", this.moduleClassLoader)) {
775+
if (ClassUtils.isPresent("kotlin.Metadata", this.moduleClassLoader)) {
776776
try {
777777
Class<? extends Module> kotlinModule = (Class<? extends Module>)
778778
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);

0 commit comments

Comments
 (0)