Skip to content

Commit 1763334

Browse files
committed
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
1 parent ffd7b93 commit 1763334

File tree

15 files changed

+56
-66
lines changed

15 files changed

+56
-66
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ public static <T> T instantiateClass(Constructor<T> ctor, @Nullable Object... ar
186186
Assert.notNull(ctor, "Constructor must not be null");
187187
try {
188188
ReflectionUtils.makeAccessible(ctor);
189-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
189+
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
190190
return KotlinDelegate.instantiateClass(ctor, args);
191191
}
192192
else {
@@ -279,7 +279,7 @@ else if (ctors.length == 0) {
279279
*/
280280
public static <T> @Nullable Constructor<T> findPrimaryConstructor(Class<T> clazz) {
281281
Assert.notNull(clazz, "Class must not be null");
282-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
282+
if (KotlinDetector.isKotlinType(clazz)) {
283283
return KotlinDelegate.findPrimaryConstructor(clazz);
284284
}
285285
if (clazz.isRecord()) {

spring-beans/src/main/java/org/springframework/beans/factory/aot/InstanceSupplierCodeGenerator.java

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -160,7 +160,7 @@ private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Cons
160160
registeredBean.getBeanName(), constructor, registeredBean.getBeanClass());
161161

162162
Class<?> publicType = descriptor.publicType();
163-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
163+
if (KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
164164
return generateCodeForInaccessibleConstructor(descriptor,
165165
hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
166166
}
@@ -408,13 +408,11 @@ private boolean isThrowingCheckedException(Executable executable) {
408408
private static class KotlinDelegate {
409409

410410
public static boolean hasConstructorWithOptionalParameter(Class<?> beanClass) {
411-
if (KotlinDetector.isKotlinType(beanClass)) {
412-
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(beanClass);
413-
for (KFunction<?> constructor : kClass.getConstructors()) {
414-
for (KParameter parameter : constructor.getParameters()) {
415-
if (parameter.isOptional()) {
416-
return true;
417-
}
411+
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(beanClass);
412+
for (KFunction<?> constructor : kClass.getConstructors()) {
413+
for (KParameter parameter : constructor.getParameters()) {
414+
if (parameter.isOptional()) {
415+
return true;
418416
}
419417
}
420418
}

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -163,9 +163,7 @@ public boolean isRequired() {
163163

164164
if (this.field != null) {
165165
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
166-
(KotlinDetector.isKotlinReflectPresent() &&
167-
KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
168-
KotlinDelegate.isNullable(this.field)));
166+
(KotlinDetector.isKotlinType(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field)));
169167
}
170168
else {
171169
return !obtainMethodParameter().isOptional();

spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ else if (void.class == factoryMethodToUse.getReturnType()) {
624624
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
625625
factoryClass.getName() + "]: needs to have a non-void return type!");
626626
}
627-
else if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(factoryMethodToUse)) {
627+
else if (KotlinDetector.isSuspendingFunction(factoryMethodToUse)) {
628628
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
629629
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
630630
factoryClass.getName() + "]: suspending functions are not supported!");

spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1092,7 +1092,7 @@ private class ReactiveCachingHandler {
10921092
() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).toFuture())));
10931093
}
10941094
}
1095-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
1095+
if (KotlinDetector.isSuspendingFunction(method)) {
10961096
return Mono.fromFuture(cache.retrieve(key, () -> {
10971097
Mono<?> mono = ((Mono<?>) invokeOperation(invoker));
10981098
if (mono == null) {

spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationReactiveSupport.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,7 +81,7 @@ abstract class ScheduledAnnotationReactiveSupport {
8181
* Kotlin coroutines bridge are not present at runtime
8282
*/
8383
public static boolean isReactive(Method method) {
84-
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(method)) {
84+
if (KotlinDetector.isSuspendingFunction(method)) {
8585
// Note that suspending functions declared without args have a single Continuation
8686
// parameter in reflective inspection
8787
Assert.isTrue(method.getParameterCount() == 1,
@@ -138,7 +138,7 @@ public static Runnable createSubscriptionRunnable(Method method, Object targetBe
138138
* to a {@code Flux} with a checkpoint String, allowing for better debugging.
139139
*/
140140
static Publisher<?> getPublisherFor(Method method, Object bean) {
141-
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(method)) {
141+
if (KotlinDetector.isSuspendingFunction(method)) {
142142
return CoroutinesUtils.invokeSuspendingFunction(method, bean, (Object[]) method.getParameters());
143143
}
144144

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

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,8 @@
2424
import org.springframework.util.ClassUtils;
2525

2626
/**
27-
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types.
27+
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types. All the methods of this class
28+
* can be safely used without any preliminary classpath checks.
2829
*
2930
* @author Juergen Hoeller
3031
* @author Sebastien Deleuze
@@ -37,6 +38,8 @@ public abstract class KotlinDetector {
3738

3839
private static final @Nullable Class<? extends Annotation> kotlinJvmInline;
3940

41+
private static final @Nullable Class<?> kotlinCoroutineContinuation;
42+
4043
// For ConstantFieldFeature compliance, otherwise could be deduced from kotlinMetadata
4144
private static final boolean kotlinPresent;
4245

@@ -46,6 +49,7 @@ public abstract class KotlinDetector {
4649
ClassLoader classLoader = KotlinDetector.class.getClassLoader();
4750
Class<?> metadata = null;
4851
Class<?> jvmInline = null;
52+
Class<?> coroutineContinuation = null;
4953
try {
5054
metadata = ClassUtils.forName("kotlin.Metadata", classLoader);
5155
try {
@@ -54,14 +58,21 @@ public abstract class KotlinDetector {
5458
catch (ClassNotFoundException ex) {
5559
// JVM inline support not available
5660
}
61+
try {
62+
coroutineContinuation = ClassUtils.forName("kotlin.coroutines.Continuation", classLoader);
63+
}
64+
catch (ClassNotFoundException ex) {
65+
// Coroutines support not available
66+
}
5767
}
5868
catch (ClassNotFoundException ex) {
5969
// Kotlin API not available - no Kotlin support
6070
}
6171
kotlinMetadata = (Class<? extends Annotation>) metadata;
6272
kotlinPresent = (kotlinMetadata != null);
63-
kotlinReflectPresent = kotlinPresent && ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
73+
kotlinReflectPresent = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
6474
kotlinJvmInline = (Class<? extends Annotation>) jvmInline;
75+
kotlinCoroutineContinuation = coroutineContinuation;
6576
}
6677

6778

@@ -89,21 +100,19 @@ public static boolean isKotlinReflectPresent() {
89100
* as invokedynamic has become the default method for lambda generation.
90101
*/
91102
public static boolean isKotlinType(Class<?> clazz) {
92-
return (kotlinMetadata != null && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
103+
return (kotlinPresent && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
93104
}
94105

95106
/**
96107
* Return {@code true} if the method is a suspending function.
97108
* @since 5.3
98109
*/
99110
public static boolean isSuspendingFunction(Method method) {
100-
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
101-
Class<?>[] types = method.getParameterTypes();
102-
if (types.length > 0 && "kotlin.coroutines.Continuation".equals(types[types.length - 1].getName())) {
103-
return true;
104-
}
111+
if (kotlinCoroutineContinuation == null) {
112+
return false;
105113
}
106-
return false;
114+
int parameterCount = method.getParameterCount();
115+
return (parameterCount > 0 && method.getParameterTypes()[parameterCount - 1] == kotlinCoroutineContinuation);
107116
}
108117

109118
/**

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,7 @@ private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) {
396396
*/
397397
public boolean isOptional() {
398398
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
399-
(KotlinDetector.isKotlinReflectPresent() &&
400-
KotlinDetector.isKotlinType(getContainingClass()) &&
401-
KotlinDelegate.isOptional(this)));
399+
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this)));
402400
}
403401

404402
/**
@@ -508,8 +506,8 @@ public Type getGenericParameterType() {
508506
if (this.parameterIndex < 0) {
509507
Method method = getMethod();
510508
paramType = (method != null ?
511-
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
512-
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
509+
(KotlinDetector.isKotlinType(getContainingClass()) ?
510+
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
513511
}
514512
else {
515513
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
@@ -536,7 +534,7 @@ private Class<?> computeParameterType() {
536534
if (method == null) {
537535
return void.class;
538536
}
539-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
537+
if (KotlinDetector.isKotlinType(getContainingClass())) {
540538
return KotlinDelegate.getReturnType(method);
541539
}
542540
return method.getReturnType();

spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ private FactoryInstantiator(Constructor<T> constructor) {
374374

375375
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
376376
Object[] args = resolveArgs(argumentResolver);
377-
if (isKotlinType(this.constructor.getDeclaringClass())) {
377+
if (KotlinDetector.isKotlinType(this.constructor.getDeclaringClass())) {
378378
return KotlinDelegate.instantiate(this.constructor, args);
379379
}
380380
return this.constructor.newInstance(args);
@@ -408,14 +408,10 @@ static <T> FactoryInstantiator<T> forClass(Class<?> factoryImplementationClass)
408408
}
409409

410410
private static @Nullable Constructor<?> findPrimaryKotlinConstructor(Class<?> factoryImplementationClass) {
411-
return (isKotlinType(factoryImplementationClass) ?
411+
return (KotlinDetector.isKotlinType(factoryImplementationClass) ?
412412
KotlinDelegate.findPrimaryConstructor(factoryImplementationClass) : null);
413413
}
414414

415-
private static boolean isKotlinType(Class<?> factoryImplementationClass) {
416-
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
417-
}
418-
419415
private static @Nullable Constructor<?> findSingleConstructor(Constructor<?>[] constructors) {
420416
return (constructors.length == 1 ? constructors[0] : null);
421417
}

spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -558,8 +558,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext context, @Nullab
558558

559559
private static boolean isKotlinProperty(Method method, String methodSuffix) {
560560
Class<?> clazz = method.getDeclaringClass();
561-
return KotlinDetector.isKotlinReflectPresent() &&
562-
KotlinDetector.isKotlinType(clazz) &&
561+
return KotlinDetector.isKotlinType(clazz) &&
563562
KotlinDelegate.isKotlinProperty(method, methodSuffix);
564563
}
565564

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -868,8 +868,7 @@ private void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> m
868868
// jackson-datatype-jsr310 not available
869869
}
870870

871-
// Kotlin present?
872-
if (KotlinDetector.isKotlinPresent()) {
871+
if (KotlinDetector.isKotlinReflectPresent()) {
873872
try {
874873
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
875874
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);

spring-web/src/main/java/org/springframework/web/method/annotation/AbstractNamedValueMethodArgumentResolver.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -103,8 +103,7 @@ public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactor
103103

104104
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
105105
MethodParameter nestedParameter = parameter.nestedIfOptional();
106-
boolean hasDefaultValue = KotlinDetector.isKotlinReflectPresent() &&
107-
KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
106+
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
108107
KotlinDelegate.hasDefaultValue(nestedParameter);
109108

110109
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
@@ -276,7 +275,7 @@ else if (paramType.isPrimitive()) {
276275

277276
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
278277
Class<?> parameterType = parameter.getParameterType();
279-
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
278+
if (KotlinDetector.isInlineClass(parameterType)) {
280279
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
281280
if (ctor != null) {
282281
parameterType = ctor.getParameterTypes()[0];

spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,11 @@ public void setMethodValidator(@Nullable MethodValidator methodValidator) {
242242
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
243243
Method method = getBridgedMethod();
244244
try {
245-
if (KotlinDetector.isKotlinReflectPresent()) {
245+
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
246246
if (KotlinDetector.isSuspendingFunction(method)) {
247247
return invokeSuspendingFunction(method, getBean(), args);
248248
}
249-
else if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
250-
return KotlinDelegate.invokeFunction(method, getBean(), args);
251-
}
249+
return KotlinDelegate.invokeFunction(method, getBean(), args);
252250
}
253251
return method.invoke(getBean(), args);
254252
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -191,12 +191,9 @@ public Mono<HandlerResult> invoke(
191191
Method method = getBridgedMethod();
192192
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
193193
try {
194-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
195-
value = KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange);
196-
}
197-
else {
198-
value = method.invoke(getBean(), args);
199-
}
194+
value = (KotlinDetector.isKotlinType(method.getDeclaringClass()) ?
195+
KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange) :
196+
method.invoke(getBean(), args));
200197
}
201198
catch (IllegalArgumentException ex) {
202199
assertTargetBean(getBridgedMethod(), getBean(), args);

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -195,7 +195,7 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
195195

196196
WebDataBinder binder = bindingContext.createDataBinder(exchange, namedValueInfo.name);
197197
Class<?> parameterType = parameter.getParameterType();
198-
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
198+
if (KotlinDetector.isInlineClass(parameterType)) {
199199
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
200200
if (ctor != null) {
201201
parameterType = ctor.getParameterTypes()[0];
@@ -222,8 +222,7 @@ private Mono<Object> getDefaultValue(NamedValueInfo namedValueInfo, String resol
222222

223223
return Mono.fromSupplier(() -> {
224224
Object value = null;
225-
boolean hasDefaultValue = KotlinDetector.isKotlinReflectPresent() &&
226-
KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
225+
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
227226
KotlinDelegate.hasDefaultValue(parameter);
228227
if (namedValueInfo.defaultValue != null) {
229228
value = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);

0 commit comments

Comments
 (0)