Skip to content

Commit

Permalink
Migrate to kotlinx.reflect.lite
Browse files Browse the repository at this point in the history
This commit migrates Spring Framework from using the "full" Kotlin
reflection API to the "light" reflection API.

Closes spring-projectsgh-21546
  • Loading branch information
mvicsokolova authored and poutsma committed Sep 7, 2022
1 parent 4cc91e4 commit 39bed4d
Show file tree
Hide file tree
Showing 27 changed files with 112 additions and 85 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ configure([rootProject] + javaProjects) { project ->
kotlinOptions {
languageVersion = "1.7"
apiVersion = "1.7"
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings", "-opt-in=kotlin.RequiresOptIn"]
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings",
"-opt-in=kotlin.RequiresOptIn", "-no-reflect"]
allWarningsAsErrors = true
}
}
Expand Down
1 change: 1 addition & 0 deletions framework-platform/framework-platform.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ dependencies {
api("org.hibernate:hibernate-validator:7.0.4.Final")
api("org.hsqldb:hsqldb:2.5.2")
api("org.javamoney:moneta:1.4.2")
api("org.jetbrains.kotlinx:kotlinx.reflect.lite:1.1.0")
api("org.jruby:jruby:9.3.4.0")
api("org.junit.support:testng-engine:1.0.4")
api("org.mozilla:rhino:1.7.11")
Expand Down
3 changes: 2 additions & 1 deletion spring-beans/spring-beans.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ dependencies {
optional("jakarta.inject:jakarta.inject-api")
optional("org.yaml:snakeyaml")
optional("org.apache.groovy:groovy-xml")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
optional("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation(testFixtures(project(":spring-core")))
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation(project(":spring-core-test"))
testImplementation("jakarta.annotation:jakarta.annotation-api")
testFixturesApi("org.junit.jupiter:junit-jupiter-api")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
import java.util.Map;
import java.util.Set;

import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.KCallablesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.reflect.lite.KFunction;
import kotlinx.reflect.lite.KParameter;
import kotlinx.reflect.lite.full.KCallablesJvm;
import kotlinx.reflect.lite.full.KClasses;
import kotlinx.reflect.lite.jvm.JvmClassMappingKt;
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;

import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.KotlinDetector;
Expand Down Expand Up @@ -187,7 +187,7 @@ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
if (KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args);
}
else {
Expand Down Expand Up @@ -274,7 +274,7 @@ else if (ctors.length == 0){
@Nullable
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
if (KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(clazz)) {
return KotlinDelegate.findPrimaryConstructor(clazz);
}
return null;
Expand Down Expand Up @@ -841,7 +841,7 @@ private static class KotlinDelegate {
@Nullable
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
try {
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
KFunction<T> primaryCtor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getLiteKClass(clazz));
if (primaryCtor == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
import java.util.Map;
import java.util.Optional;

import kotlin.reflect.KProperty;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.reflect.lite.KProperty;
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
Expand Down Expand Up @@ -169,7 +169,7 @@ public boolean isRequired() {

if (this.field != null) {
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
(KotlinDetector.isKotlinReflectLitePresent() &&
KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
KotlinDelegate.isNullable(this.field)));
}
Expand Down
2 changes: 1 addition & 1 deletion spring-context/spring-context.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies {
optional("org.apache.groovy:groovy")
optional("org.apache-extras.beanshell:bsh")
optional("org.hibernate:hibernate-validator")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
optional("org.jetbrains.kotlin:kotlin-stdlib")
optional("org.reactivestreams:reactive-streams")
testImplementation(project(":spring-core-test"))
Expand Down
2 changes: 1 addition & 1 deletion spring-core/spring-core.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ dependencies {
compileOnly("io.projectreactor.tools:blockhound")
optional("net.sf.jopt-simple:jopt-simple")
optional("org.aspectj:aspectjweaver")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
optional("org.jetbrains.kotlin:kotlin-stdlib")
optional("org.jetbrains.kotlinx:kotlinx-coroutines-core")
optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@
import java.util.Objects;

import kotlin.Unit;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import kotlin.reflect.KClassifier;
import kotlin.reflect.KFunction;
import kotlin.reflect.full.KCallables;
import kotlin.reflect.jvm.KCallablesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.coroutines.BuildersKt;
import kotlinx.coroutines.CoroutineStart;
import kotlinx.coroutines.Deferred;
Expand All @@ -36,6 +29,13 @@
import kotlinx.coroutines.flow.Flow;
import kotlinx.coroutines.reactor.MonoKt;
import kotlinx.coroutines.reactor.ReactorFlowKt;
import kotlinx.reflect.lite.KClass;
import kotlinx.reflect.lite.KClassifier;
import kotlinx.reflect.lite.KFunction;
import kotlinx.reflect.lite.full.KCallables;
import kotlinx.reflect.lite.full.KCallablesJvm;
import kotlinx.reflect.lite.jvm.JvmClassMappingKt;
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
Expand Down Expand Up @@ -83,10 +83,10 @@ public static Publisher<?> invokeSuspendingFunction(Method method, Object target

KClassifier returnType = function.getReturnType().getClassifier();
if (returnType != null) {
if (returnType.equals(JvmClassMappingKt.getKotlinClass(Flow.class))) {
if (returnType.equals(JvmClassMappingKt.getLiteKClass(Flow.class))) {
return mono.flatMapMany(CoroutinesUtils::asFlux);
}
else if (returnType.equals(JvmClassMappingKt.getKotlinClass(Mono.class))) {
else if (returnType.equals(JvmClassMappingKt.getLiteKClass(Mono.class))) {
return mono.flatMap(o -> ((Mono<?>)o));
}
else if (returnType instanceof KClass<?> kClass &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent()) {
if (KotlinDetector.isKotlinReflectLitePresent()) {
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public abstract class KotlinDetector {

private static final boolean kotlinReflectPresent;

private static final boolean kotlinReflectLitePresent;

static {
Class<?> metadata;
ClassLoader classLoader = KotlinDetector.class.getClassLoader();
Expand All @@ -53,6 +55,7 @@ public abstract class KotlinDetector {
kotlinMetadata = (Class<? extends Annotation>) metadata;
kotlinPresent = (kotlinMetadata != null);
kotlinReflectPresent = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
kotlinReflectLitePresent = ClassUtils.isPresent("kotlinx.reflect.lite.full.KCallables", classLoader);
}


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

/**
* Determine whether Kotlin reflection is present.
* Determine whether the Kotlin reflection API is present.
* @since 5.1
* @deprecated as of 6.0, in favor of {@link #isKotlinReflectLitePresent()},
* as Spring Framework does not require the "full" Kotlin reflection API
* anymore
*/
@Deprecated
public static boolean isKotlinReflectPresent() {
return kotlinReflectPresent;
}

/**
* Determine whether the Kotlin "light" reflection API is present.
* @since 6.0
*/
public static boolean isKotlinReflectLitePresent() {
return kotlinReflectLitePresent;
}

/**
* Determine whether the given {@code Class} is a Kotlin type
* (with Kotlin metadata present on it).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
import java.util.List;
import java.util.stream.Collectors;

import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.reflect.lite.KFunction;
import kotlinx.reflect.lite.KParameter;
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;

import org.springframework.lang.Nullable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
import java.util.function.Predicate;

import kotlin.Unit;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.reflect.lite.KFunction;
import kotlinx.reflect.lite.KParameter;
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -402,7 +402,7 @@ private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) {
*/
public boolean isOptional() {
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
(KotlinDetector.isKotlinReflectLitePresent() &&
KotlinDetector.isKotlinType(getContainingClass()) &&
KotlinDelegate.isOptional(this)));
}
Expand Down Expand Up @@ -506,7 +506,7 @@ public Type getGenericParameterType() {
if (this.parameterIndex < 0) {
Method method = getMethod();
paramType = (method != null ?
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
(KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
}
else {
Expand Down Expand Up @@ -534,7 +534,7 @@ private Class<?> computeParameterType() {
if (method == null) {
return void.class;
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
if (KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method);
}
return method.getReturnType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
import java.util.function.Function;
import java.util.function.Supplier;

import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.KCallablesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;
import kotlinx.reflect.lite.KFunction;
import kotlinx.reflect.lite.KParameter;
import kotlinx.reflect.lite.full.KCallablesJvm;
import kotlinx.reflect.lite.full.KClasses;
import kotlinx.reflect.lite.jvm.JvmClassMappingKt;
import kotlinx.reflect.lite.jvm.ReflectJvmMapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Expand Down Expand Up @@ -420,7 +420,7 @@ private static Constructor<?> findPrimaryKotlinConstructor(Class<?> factoryImple
}

private static boolean isKotlinType(Class<?> factoryImplementationClass) {
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
return KotlinDetector.isKotlinReflectLitePresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
}

@Nullable
Expand Down Expand Up @@ -450,7 +450,7 @@ private static class KotlinDelegate {
@Nullable
static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
try {
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getLiteKClass(clazz));
if (primaryConstructor != null) {
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(
primaryConstructor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

package org.springframework.core

import kotlinx.reflect.lite.KFunction
import kotlinx.reflect.lite.jvm.javaMethod
import kotlinx.reflect.lite.jvm.kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import java.lang.reflect.Method
import java.lang.reflect.TypeVariable
import kotlin.coroutines.Continuation
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.jvm.javaMethod

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

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

@Suppress("unused_parameter")
fun nullable(nullable: String?): Int? = 42
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import kotlinx.reflect.lite.KClass
import kotlinx.reflect.lite.jvm.java
import kotlinx.reflect.lite.jvm.kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import java.time.Duration
import kotlin.reflect.KClass

@OptIn(DelicateCoroutinesApi::class)
class KotlinReactiveAdapterRegistryTests {
Expand All @@ -41,15 +43,15 @@ class KotlinReactiveAdapterRegistryTests {
@Test
fun deferredToPublisher() {
val source = GlobalScope.async { 1 }
val target: Publisher<Int> = getAdapter(Deferred::class).toPublisher(source)
val target: Publisher<Int> = getAdapter((Deferred::class.java).kotlin).toPublisher(source)
assertThat(target).isInstanceOf(Mono::class.java)
assertThat((target as Mono<Int>).block(Duration.ofMillis(1000))).isEqualTo(1)
}

@Test
fun publisherToDeferred() {
val source = Mono.just(1)
val target = getAdapter(Deferred::class).fromPublisher(source)
val target = getAdapter((Deferred::class.java).kotlin).fromPublisher(source)
assertThat(target).isInstanceOf(Deferred::class.java)
assertThat(runBlocking { (target as Deferred<*>).await() }).isEqualTo(1)
}
Expand All @@ -61,7 +63,7 @@ class KotlinReactiveAdapterRegistryTests {
emit(2)
emit(3)
}
val target: Publisher<Int> = getAdapter(Flow::class).toPublisher(source)
val target: Publisher<Int> = getAdapter((Flow::class.java).kotlin).toPublisher(source)
assertThat(target).isInstanceOf(Flux::class.java)
StepVerifier.create(target)
.expectNext(1)
Expand All @@ -73,7 +75,7 @@ class KotlinReactiveAdapterRegistryTests {
@Test
fun publisherToFlow() {
val source = Flux.just(1, 2, 3)
val target = getAdapter(Flow::class).fromPublisher(source)
val target = getAdapter((Flow::class.java).kotlin).fromPublisher(source)
assertThat(target).isInstanceOf(Flow::class.java)
assertThat(runBlocking { (target as Flow<*>).toList() }).contains(1, 2, 3)
}
Expand Down
2 changes: 1 addition & 1 deletion spring-jdbc/spring-jdbc.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies {
optional("com.h2database:h2")
optional("org.apache.derby:derby")
optional("org.apache.derby:derbyclient")
optional("org.jetbrains.kotlin:kotlin-reflect")
optional("org.jetbrains.kotlinx:kotlinx.reflect.lite")
optional("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation(testFixtures(project(":spring-beans")))
testImplementation(testFixtures(project(":spring-core")))
Expand Down
2 changes: 1 addition & 1 deletion spring-messaging/spring-messaging.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies {
testImplementation("org.apache.activemq:activemq-stomp")
testImplementation("io.projectreactor:reactor-test")
testImplementation("io.reactivex.rxjava3:rxjava")
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.jetbrains.kotlinx:kotlinx.reflect.lite")
testImplementation("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation("org.xmlunit:xmlunit-assertj")
testImplementation("org.xmlunit:xmlunit-matchers")
Expand Down
Loading

0 comments on commit 39bed4d

Please sign in to comment.