Refine KotlinDetector usages and implementation

This commit refines KotlinDetector usages and implementation in order
to remove preliminary KotlinDetector#isKotlinReflectPresent invocations
and to ensure that KotlinDetector methods are implemented safely and
efficiently for such use case.

Closes gh-34275
This commit is contained in:
Sébastien Deleuze 2025-01-17 16:21:50 +01:00
parent ffd7b93dde
commit 1763334180
15 changed files with 56 additions and 66 deletions

View File

@ -186,7 +186,7 @@ public abstract class BeanUtils {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args);
}
else {
@ -279,7 +279,7 @@ public abstract class BeanUtils {
*/
public static <T> @Nullable Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
if (KotlinDetector.isKotlinType(clazz)) {
return KotlinDelegate.findPrimaryConstructor(clazz);
}
if (clazz.isRecord()) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -160,7 +160,7 @@ public class InstanceSupplierCodeGenerator {
registeredBean.getBeanName(), constructor, registeredBean.getBeanClass());
Class<?> publicType = descriptor.publicType();
if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
if (KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
return generateCodeForInaccessibleConstructor(descriptor,
hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
}
@ -408,13 +408,11 @@ public class InstanceSupplierCodeGenerator {
private static class KotlinDelegate {
public static boolean hasConstructorWithOptionalParameter(Class<?> beanClass) {
if (KotlinDetector.isKotlinType(beanClass)) {
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(beanClass);
for (KFunction<?> constructor : kClass.getConstructors()) {
for (KParameter parameter : constructor.getParameters()) {
if (parameter.isOptional()) {
return true;
}
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(beanClass);
for (KFunction<?> constructor : kClass.getConstructors()) {
for (KParameter parameter : constructor.getParameters()) {
if (parameter.isOptional()) {
return true;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -163,9 +163,7 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
if (this.field != null) {
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
KotlinDelegate.isNullable(this.field)));
(KotlinDetector.isKotlinType(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field)));
}
else {
return !obtainMethodParameter().isOptional();

View File

@ -624,7 +624,7 @@ class ConstructorResolver {
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
factoryClass.getName() + "]: needs to have a non-void return type!");
}
else if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(factoryMethodToUse)) {
else if (KotlinDetector.isSuspendingFunction(factoryMethodToUse)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
factoryClass.getName() + "]: suspending functions are not supported!");

View File

@ -1092,7 +1092,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).toFuture())));
}
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
if (KotlinDetector.isSuspendingFunction(method)) {
return Mono.fromFuture(cache.retrieve(key, () -> {
Mono<?> mono = ((Mono<?>) invokeOperation(invoker));
if (mono == null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -81,7 +81,7 @@ abstract class ScheduledAnnotationReactiveSupport {
* Kotlin coroutines bridge are not present at runtime
*/
public static boolean isReactive(Method method) {
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(method)) {
if (KotlinDetector.isSuspendingFunction(method)) {
// Note that suspending functions declared without args have a single Continuation
// parameter in reflective inspection
Assert.isTrue(method.getParameterCount() == 1,
@ -138,7 +138,7 @@ abstract class ScheduledAnnotationReactiveSupport {
* to a {@code Flux} with a checkpoint String, allowing for better debugging.
*/
static Publisher<?> getPublisherFor(Method method, Object bean) {
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(method)) {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, bean, (Object[]) method.getParameters());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -24,7 +24,8 @@ import org.jspecify.annotations.Nullable;
import org.springframework.util.ClassUtils;
/**
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types.
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types. All the methods of this class
* can be safely used without any preliminary classpath checks.
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
@ -37,6 +38,8 @@ public abstract class KotlinDetector {
private static final @Nullable Class<? extends Annotation> kotlinJvmInline;
private static final @Nullable Class<?> kotlinCoroutineContinuation;
// For ConstantFieldFeature compliance, otherwise could be deduced from kotlinMetadata
private static final boolean kotlinPresent;
@ -46,6 +49,7 @@ public abstract class KotlinDetector {
ClassLoader classLoader = KotlinDetector.class.getClassLoader();
Class<?> metadata = null;
Class<?> jvmInline = null;
Class<?> coroutineContinuation = null;
try {
metadata = ClassUtils.forName("kotlin.Metadata", classLoader);
try {
@ -54,14 +58,21 @@ public abstract class KotlinDetector {
catch (ClassNotFoundException ex) {
// JVM inline support not available
}
try {
coroutineContinuation = ClassUtils.forName("kotlin.coroutines.Continuation", classLoader);
}
catch (ClassNotFoundException ex) {
// Coroutines support not available
}
}
catch (ClassNotFoundException ex) {
// Kotlin API not available - no Kotlin support
}
kotlinMetadata = (Class<? extends Annotation>) metadata;
kotlinPresent = (kotlinMetadata != null);
kotlinReflectPresent = kotlinPresent && ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
kotlinReflectPresent = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
kotlinJvmInline = (Class<? extends Annotation>) jvmInline;
kotlinCoroutineContinuation = coroutineContinuation;
}
@ -89,7 +100,7 @@ public abstract class KotlinDetector {
* as invokedynamic has become the default method for lambda generation.
*/
public static boolean isKotlinType(Class<?> clazz) {
return (kotlinMetadata != null && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
return (kotlinPresent && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
}
/**
@ -97,13 +108,11 @@ public abstract class KotlinDetector {
* @since 5.3
*/
public static boolean isSuspendingFunction(Method method) {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] types = method.getParameterTypes();
if (types.length > 0 && "kotlin.coroutines.Continuation".equals(types[types.length - 1].getName())) {
return true;
}
if (kotlinCoroutineContinuation == null) {
return false;
}
return false;
int parameterCount = method.getParameterCount();
return (parameterCount > 0 && method.getParameterTypes()[parameterCount - 1] == kotlinCoroutineContinuation);
}
/**

View File

@ -396,9 +396,7 @@ public class MethodParameter {
*/
public boolean isOptional() {
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(getContainingClass()) &&
KotlinDelegate.isOptional(this)));
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this)));
}
/**
@ -508,8 +506,8 @@ public class MethodParameter {
if (this.parameterIndex < 0) {
Method method = getMethod();
paramType = (method != null ?
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
(KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
}
else {
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
@ -536,7 +534,7 @@ public class MethodParameter {
if (method == null) {
return void.class;
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
if (KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method);
}
return method.getReturnType();

View File

@ -374,7 +374,7 @@ public class SpringFactoriesLoader {
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
Object[] args = resolveArgs(argumentResolver);
if (isKotlinType(this.constructor.getDeclaringClass())) {
if (KotlinDetector.isKotlinType(this.constructor.getDeclaringClass())) {
return KotlinDelegate.instantiate(this.constructor, args);
}
return this.constructor.newInstance(args);
@ -408,14 +408,10 @@ public class SpringFactoriesLoader {
}
private static @Nullable Constructor<?> findPrimaryKotlinConstructor(Class<?> factoryImplementationClass) {
return (isKotlinType(factoryImplementationClass) ?
return (KotlinDetector.isKotlinType(factoryImplementationClass) ?
KotlinDelegate.findPrimaryConstructor(factoryImplementationClass) : null);
}
private static boolean isKotlinType(Class<?> factoryImplementationClass) {
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
}
private static @Nullable Constructor<?> findSingleConstructor(Constructor<?>[] constructors) {
return (constructors.length == 1 ? constructors[0] : null);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -558,8 +558,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private static boolean isKotlinProperty(Method method, String methodSuffix) {
Class<?> clazz = method.getDeclaringClass();
return KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(clazz) &&
return KotlinDetector.isKotlinType(clazz) &&
KotlinDelegate.isKotlinProperty(method, methodSuffix);
}

View File

@ -868,8 +868,7 @@ public class Jackson2ObjectMapperBuilder {
// jackson-datatype-jsr310 not available
}
// Kotlin present?
if (KotlinDetector.isKotlinPresent()) {
if (KotlinDetector.isKotlinReflectPresent()) {
try {
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -103,8 +103,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
boolean hasDefaultValue = KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(nestedParameter);
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
@ -276,7 +275,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
Class<?> parameterType = parameter.getParameterType();
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
if (KotlinDetector.isInlineClass(parameterType)) {
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
if (ctor != null) {
parameterType = ctor.getParameterTypes()[0];

View File

@ -242,13 +242,11 @@ public class InvocableHandlerMethod extends HandlerMethod {
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isKotlinReflectPresent()) {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KotlinDetector.isSuspendingFunction(method)) {
return invokeSuspendingFunction(method, getBean(), args);
}
else if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
return KotlinDelegate.invokeFunction(method, getBean(), args);
}
return KotlinDelegate.invokeFunction(method, getBean(), args);
}
return method.invoke(getBean(), args);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -191,12 +191,9 @@ public class InvocableHandlerMethod extends HandlerMethod {
Method method = getBridgedMethod();
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
try {
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
value = KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange);
}
else {
value = method.invoke(getBean(), args);
}
value = (KotlinDetector.isKotlinType(method.getDeclaringClass()) ?
KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange) :
method.invoke(getBean(), args));
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@ -195,7 +195,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
WebDataBinder binder = bindingContext.createDataBinder(exchange, namedValueInfo.name);
Class<?> parameterType = parameter.getParameterType();
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
if (KotlinDetector.isInlineClass(parameterType)) {
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
if (ctor != null) {
parameterType = ctor.getParameterTypes()[0];
@ -222,8 +222,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
return Mono.fromSupplier(() -> {
Object value = null;
boolean hasDefaultValue = KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(parameter);
if (namedValueInfo.defaultValue != null) {
value = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);