Skip to content

Commit 39bed4d

Browse files
mvicsokolovapoutsma
authored andcommitted
Migrate to kotlinx.reflect.lite
This commit migrates Spring Framework from using the "full" Kotlin reflection API to the "light" reflection API. Closes spring-projectsgh-21546
1 parent 4cc91e4 commit 39bed4d

File tree

27 files changed

+112
-85
lines changed

27 files changed

+112
-85
lines changed

build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ configure([rootProject] + javaProjects) { project ->
6262
kotlinOptions {
6363
languageVersion = "1.7"
6464
apiVersion = "1.7"
65-
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings", "-opt-in=kotlin.RequiresOptIn"]
65+
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings",
66+
"-opt-in=kotlin.RequiresOptIn", "-no-reflect"]
6667
allWarningsAsErrors = true
6768
}
6869
}

framework-platform/framework-platform.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ dependencies {
124124
api("org.hibernate:hibernate-validator:7.0.4.Final")
125125
api("org.hsqldb:hsqldb:2.5.2")
126126
api("org.javamoney:moneta:1.4.2")
127+
api("org.jetbrains.kotlinx:kotlinx.reflect.lite:1.1.0")
127128
api("org.jruby:jruby:9.3.4.0")
128129
api("org.junit.support:testng-engine:1.0.4")
129130
api("org.mozilla:rhino:1.7.11")

spring-beans/spring-beans.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ dependencies {
77
optional("jakarta.inject:jakarta.inject-api")
88
optional("org.yaml:snakeyaml")
99
optional("org.apache.groovy:groovy-xml")
10-
optional("org.jetbrains.kotlin:kotlin-reflect")
10+
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
1111
optional("org.jetbrains.kotlin:kotlin-stdlib")
1212
testImplementation(testFixtures(project(":spring-core")))
13+
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
1314
testImplementation(project(":spring-core-test"))
1415
testImplementation("jakarta.annotation:jakarta.annotation-api")
1516
testFixturesApi("org.junit.jupiter:junit-jupiter-api")

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@
3434
import java.util.Map;
3535
import java.util.Set;
3636

37-
import kotlin.jvm.JvmClassMappingKt;
38-
import kotlin.reflect.KFunction;
39-
import kotlin.reflect.KParameter;
40-
import kotlin.reflect.full.KClasses;
41-
import kotlin.reflect.jvm.KCallablesJvm;
42-
import kotlin.reflect.jvm.ReflectJvmMapping;
37+
import kotlinx.reflect.lite.KFunction;
38+
import kotlinx.reflect.lite.KParameter;
39+
import kotlinx.reflect.lite.full.KCallablesJvm;
40+
import kotlinx.reflect.lite.full.KClasses;
41+
import kotlinx.reflect.lite.jvm.JvmClassMappingKt;
42+
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
4343

4444
import org.springframework.core.DefaultParameterNameDiscoverer;
4545
import org.springframework.core.KotlinDetector;
@@ -187,7 +187,7 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws
187187
Assert.notNull(ctor, "Constructor must not be null");
188188
try {
189189
ReflectionUtils.makeAccessible(ctor);
190-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
190+
if (KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
191191
return KotlinDelegate.instantiateClass(ctor, args);
192192
}
193193
else {
@@ -274,7 +274,7 @@ else if (ctors.length == 0){
274274
@Nullable
275275
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
276276
Assert.notNull(clazz, "Class must not be null");
277-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
277+
if (KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(clazz)) {
278278
return KotlinDelegate.findPrimaryConstructor(clazz);
279279
}
280280
return null;
@@ -841,7 +841,7 @@ private static class KotlinDelegate {
841841
@Nullable
842842
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
843843
try {
844-
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
844+
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getLiteKClass(clazz));
845845
if (primaryCtor == null) {
846846
return null;
847847
}

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import java.util.Map;
2525
import java.util.Optional;
2626

27-
import kotlin.reflect.KProperty;
28-
import kotlin.reflect.jvm.ReflectJvmMapping;
27+
import kotlinx.reflect.lite.KProperty;
28+
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
2929

3030
import org.springframework.beans.BeansException;
3131
import org.springframework.beans.factory.BeanFactory;
@@ -169,7 +169,7 @@ public boolean isRequired() {
169169

170170
if (this.field != null) {
171171
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
172-
(KotlinDetector.isKotlinReflectPresent() &&
172+
(KotlinDetector.isKotlinReflectLitePresent() &&
173173
KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
174174
KotlinDelegate.isNullable(this.field)));
175175
}

spring-context/spring-context.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dependencies {
2323
optional("org.apache.groovy:groovy")
2424
optional("org.apache-extras.beanshell:bsh")
2525
optional("org.hibernate:hibernate-validator")
26-
optional("org.jetbrains.kotlin:kotlin-reflect")
26+
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
2727
optional("org.jetbrains.kotlin:kotlin-stdlib")
2828
optional("org.reactivestreams:reactive-streams")
2929
testImplementation(project(":spring-core-test"))

spring-core/spring-core.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ dependencies {
6565
compileOnly("io.projectreactor.tools:blockhound")
6666
optional("net.sf.jopt-simple:jopt-simple")
6767
optional("org.aspectj:aspectjweaver")
68-
optional("org.jetbrains.kotlin:kotlin-reflect")
68+
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
6969
optional("org.jetbrains.kotlin:kotlin-stdlib")
7070
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core")
7171
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@
2121
import java.util.Objects;
2222

2323
import kotlin.Unit;
24-
import kotlin.jvm.JvmClassMappingKt;
25-
import kotlin.reflect.KClass;
26-
import kotlin.reflect.KClassifier;
27-
import kotlin.reflect.KFunction;
28-
import kotlin.reflect.full.KCallables;
29-
import kotlin.reflect.jvm.KCallablesJvm;
30-
import kotlin.reflect.jvm.ReflectJvmMapping;
3124
import kotlinx.coroutines.BuildersKt;
3225
import kotlinx.coroutines.CoroutineStart;
3326
import kotlinx.coroutines.Deferred;
@@ -36,6 +29,13 @@
3629
import kotlinx.coroutines.flow.Flow;
3730
import kotlinx.coroutines.reactor.MonoKt;
3831
import kotlinx.coroutines.reactor.ReactorFlowKt;
32+
import kotlinx.reflect.lite.KClass;
33+
import kotlinx.reflect.lite.KClassifier;
34+
import kotlinx.reflect.lite.KFunction;
35+
import kotlinx.reflect.lite.full.KCallables;
36+
import kotlinx.reflect.lite.full.KCallablesJvm;
37+
import kotlinx.reflect.lite.jvm.JvmClassMappingKt;
38+
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
3939
import org.reactivestreams.Publisher;
4040
import reactor.core.publisher.Flux;
4141
import reactor.core.publisher.Mono;
@@ -83,10 +83,10 @@ public static Publisher<?> invokeSuspendingFunction(Method method, Object target
8383

8484
KClassifier returnType = function.getReturnType().getClassifier();
8585
if (returnType != null) {
86-
if (returnType.equals(JvmClassMappingKt.getKotlinClass(Flow.class))) {
86+
if (returnType.equals(JvmClassMappingKt.getLiteKClass(Flow.class))) {
8787
return mono.flatMapMany(CoroutinesUtils::asFlux);
8888
}
89-
else if (returnType.equals(JvmClassMappingKt.getKotlinClass(Mono.class))) {
89+
else if (returnType.equals(JvmClassMappingKt.getLiteKClass(Mono.class))) {
9090
return mono.flatMap(o -> ((Mono<?>)o));
9191
}
9292
else if (returnType instanceof KClass<?> kClass &&

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
4141

4242
public DefaultParameterNameDiscoverer() {
43-
if (KotlinDetector.isKotlinReflectPresent()) {
43+
if (KotlinDetector.isKotlinReflectLitePresent()) {
4444
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
4545
}
4646
addDiscoverer(new StandardReflectionParameterNameDiscoverer());

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ public abstract class KotlinDetector {
4040

4141
private static final boolean kotlinReflectPresent;
4242

43+
private static final boolean kotlinReflectLitePresent;
44+
4345
static {
4446
Class<?> metadata;
4547
ClassLoader classLoader = KotlinDetector.class.getClassLoader();
@@ -53,6 +55,7 @@ public abstract class KotlinDetector {
5355
kotlinMetadata = (Class<? extends Annotation>) metadata;
5456
kotlinPresent = (kotlinMetadata != null);
5557
kotlinReflectPresent = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
58+
kotlinReflectLitePresent = ClassUtils.isPresent("kotlinx.reflect.lite.full.KCallables", classLoader);
5659
}
5760

5861

@@ -64,13 +67,25 @@ public static boolean isKotlinPresent() {
6467
}
6568

6669
/**
67-
* Determine whether Kotlin reflection is present.
70+
* Determine whether the Kotlin reflection API is present.
6871
* @since 5.1
72+
* @deprecated as of 6.0, in favor of {@link #isKotlinReflectLitePresent()},
73+
* as Spring Framework does not require the "full" Kotlin reflection API
74+
* anymore
6975
*/
76+
@Deprecated
7077
public static boolean isKotlinReflectPresent() {
7178
return kotlinReflectPresent;
7279
}
7380

81+
/**
82+
* Determine whether the Kotlin "light" reflection API is present.
83+
* @since 6.0
84+
*/
85+
public static boolean isKotlinReflectLitePresent() {
86+
return kotlinReflectLitePresent;
87+
}
88+
7489
/**
7590
* Determine whether the given {@code Class} is a Kotlin type
7691
* (with Kotlin metadata present on it).

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import java.util.List;
2222
import java.util.stream.Collectors;
2323

24-
import kotlin.reflect.KFunction;
25-
import kotlin.reflect.KParameter;
26-
import kotlin.reflect.jvm.ReflectJvmMapping;
24+
import kotlinx.reflect.lite.KFunction;
25+
import kotlinx.reflect.lite.KParameter;
26+
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
2727

2828
import org.springframework.lang.Nullable;
2929

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
import java.util.function.Predicate;
3232

3333
import kotlin.Unit;
34-
import kotlin.reflect.KFunction;
35-
import kotlin.reflect.KParameter;
36-
import kotlin.reflect.jvm.ReflectJvmMapping;
34+
import kotlinx.reflect.lite.KFunction;
35+
import kotlinx.reflect.lite.KParameter;
36+
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
3737

3838
import org.springframework.lang.Nullable;
3939
import org.springframework.util.Assert;
@@ -402,7 +402,7 @@ private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) {
402402
*/
403403
public boolean isOptional() {
404404
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
405-
(KotlinDetector.isKotlinReflectPresent() &&
405+
(KotlinDetector.isKotlinReflectLitePresent() &&
406406
KotlinDetector.isKotlinType(getContainingClass()) &&
407407
KotlinDelegate.isOptional(this)));
408408
}
@@ -506,7 +506,7 @@ public Type getGenericParameterType() {
506506
if (this.parameterIndex < 0) {
507507
Method method = getMethod();
508508
paramType = (method != null ?
509-
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
509+
(KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
510510
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
511511
}
512512
else {
@@ -534,7 +534,7 @@ private Class<?> computeParameterType() {
534534
if (method == null) {
535535
return void.class;
536536
}
537-
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
537+
if (KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(getContainingClass())) {
538538
return KotlinDelegate.getReturnType(method);
539539
}
540540
return method.getReturnType();

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333
import java.util.function.Function;
3434
import java.util.function.Supplier;
3535

36-
import kotlin.jvm.JvmClassMappingKt;
37-
import kotlin.reflect.KFunction;
38-
import kotlin.reflect.KParameter;
39-
import kotlin.reflect.full.KClasses;
40-
import kotlin.reflect.jvm.KCallablesJvm;
41-
import kotlin.reflect.jvm.ReflectJvmMapping;
36+
import kotlinx.reflect.lite.KFunction;
37+
import kotlinx.reflect.lite.KParameter;
38+
import kotlinx.reflect.lite.full.KCallablesJvm;
39+
import kotlinx.reflect.lite.full.KClasses;
40+
import kotlinx.reflect.lite.jvm.JvmClassMappingKt;
41+
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
4242
import org.apache.commons.logging.Log;
4343
import org.apache.commons.logging.LogFactory;
4444

@@ -420,7 +420,7 @@ private static Constructor<?> findPrimaryKotlinConstructor(Class<?> factoryImple
420420
}
421421

422422
private static boolean isKotlinType(Class<?> factoryImplementationClass) {
423-
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
423+
return KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
424424
}
425425

426426
@Nullable
@@ -450,7 +450,7 @@ private static class KotlinDelegate {
450450
@Nullable
451451
static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
452452
try {
453-
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
453+
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getLiteKClass(clazz));
454454
if (primaryConstructor != null) {
455455
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(
456456
primaryConstructor);

spring-core/src/test/kotlin/org/springframework/core/KotlinMethodParameterTests.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616

1717
package org.springframework.core
1818

19+
import kotlinx.reflect.lite.KFunction
20+
import kotlinx.reflect.lite.jvm.javaMethod
21+
import kotlinx.reflect.lite.jvm.kotlin
1922
import org.assertj.core.api.Assertions.assertThat
2023
import org.junit.jupiter.api.Test
2124
import java.lang.reflect.Method
2225
import java.lang.reflect.TypeVariable
2326
import kotlin.coroutines.Continuation
24-
import kotlin.reflect.full.declaredFunctions
25-
import kotlin.reflect.jvm.javaMethod
2627

2728
/**
2829
* Tests for Kotlin support in [MethodParameter].
@@ -115,7 +116,7 @@ class KotlinMethodParameterTests {
115116
private fun returnGenericParameterTypeBoundName(funName: String) = (returnGenericParameterType(funName) as TypeVariable<*>).bounds[0].typeName
116117

117118
private fun returnMethodParameter(funName: String) =
118-
MethodParameter(this::class.declaredFunctions.first { it.name == funName }.javaMethod!!, -1)
119+
MethodParameter((this::class.java).kotlin.members.filterIsInstance<KFunction<*>>().first { it.name == funName }.javaMethod!!, -1)
119120

120121
@Suppress("unused_parameter")
121122
fun nullable(nullable: String?): Int? = 42

spring-core/src/test/kotlin/org/springframework/core/KotlinReactiveAdapterRegistryTests.kt

+7-5
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ import kotlinx.coroutines.flow.Flow
2424
import kotlinx.coroutines.flow.flow
2525
import kotlinx.coroutines.flow.toList
2626
import kotlinx.coroutines.runBlocking
27+
import kotlinx.reflect.lite.KClass
28+
import kotlinx.reflect.lite.jvm.java
29+
import kotlinx.reflect.lite.jvm.kotlin
2730
import org.assertj.core.api.Assertions.assertThat
2831
import org.junit.jupiter.api.Test
2932
import org.reactivestreams.Publisher
3033
import reactor.core.publisher.Flux
3134
import reactor.core.publisher.Mono
3235
import reactor.test.StepVerifier
3336
import java.time.Duration
34-
import kotlin.reflect.KClass
3537

3638
@OptIn(DelicateCoroutinesApi::class)
3739
class KotlinReactiveAdapterRegistryTests {
@@ -41,15 +43,15 @@ class KotlinReactiveAdapterRegistryTests {
4143
@Test
4244
fun deferredToPublisher() {
4345
val source = GlobalScope.async { 1 }
44-
val target: Publisher<Int> = getAdapter(Deferred::class).toPublisher(source)
46+
val target: Publisher<Int> = getAdapter((Deferred::class.java).kotlin).toPublisher(source)
4547
assertThat(target).isInstanceOf(Mono::class.java)
4648
assertThat((target as Mono<Int>).block(Duration.ofMillis(1000))).isEqualTo(1)
4749
}
4850

4951
@Test
5052
fun publisherToDeferred() {
5153
val source = Mono.just(1)
52-
val target = getAdapter(Deferred::class).fromPublisher(source)
54+
val target = getAdapter((Deferred::class.java).kotlin).fromPublisher(source)
5355
assertThat(target).isInstanceOf(Deferred::class.java)
5456
assertThat(runBlocking { (target as Deferred<*>).await() }).isEqualTo(1)
5557
}
@@ -61,7 +63,7 @@ class KotlinReactiveAdapterRegistryTests {
6163
emit(2)
6264
emit(3)
6365
}
64-
val target: Publisher<Int> = getAdapter(Flow::class).toPublisher(source)
66+
val target: Publisher<Int> = getAdapter((Flow::class.java).kotlin).toPublisher(source)
6567
assertThat(target).isInstanceOf(Flux::class.java)
6668
StepVerifier.create(target)
6769
.expectNext(1)
@@ -73,7 +75,7 @@ class KotlinReactiveAdapterRegistryTests {
7375
@Test
7476
fun publisherToFlow() {
7577
val source = Flux.just(1, 2, 3)
76-
val target = getAdapter(Flow::class).fromPublisher(source)
78+
val target = getAdapter((Flow::class.java).kotlin).fromPublisher(source)
7779
assertThat(target).isInstanceOf(Flow::class.java)
7880
assertThat(runBlocking { (target as Flow<*>).toList() }).contains(1, 2, 3)
7981
}

spring-jdbc/spring-jdbc.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ dependencies {
1212
optional("com.h2database:h2")
1313
optional("org.apache.derby:derby")
1414
optional("org.apache.derby:derbyclient")
15-
optional("org.jetbrains.kotlin:kotlin-reflect")
15+
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
1616
optional("org.jetbrains.kotlin:kotlin-stdlib")
1717
testImplementation(testFixtures(project(":spring-beans")))
1818
testImplementation(testFixtures(project(":spring-core")))

spring-messaging/spring-messaging.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dependencies {
2828
testImplementation("org.apache.activemq:activemq-stomp")
2929
testImplementation("io.projectreactor:reactor-test")
3030
testImplementation("io.reactivex.rxjava3:rxjava")
31-
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
31+
testImplementation("org.jetbrains.kotlinx:kotlinx.reflect.lite")
3232
testImplementation("org.jetbrains.kotlin:kotlin-stdlib")
3333
testImplementation("org.xmlunit:xmlunit-assertj")
3434
testImplementation("org.xmlunit:xmlunit-matchers")

0 commit comments

Comments
 (0)