Browse Source

Support Kotlin value class properties in SpEL

This commit adds support for inlined Kotlin value
class properties in SpEL by leveraging Kotlin
reflection instead of Java reflection broken
by the mangled method name.

Closes gh-30468
pull/31063/head
Sébastien Deleuze 1 year ago
parent
commit
1e73439955
  1. 1
      spring-expression/spring-expression.gradle
  2. 39
      spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java
  3. 21
      spring-expression/src/test/kotlin/org/springframework/expression/spel/SpelReproKotlinTests.kt

1
spring-expression/spring-expression.gradle

@ -4,6 +4,7 @@ apply plugin: "kotlin" @@ -4,6 +4,7 @@ apply plugin: "kotlin"
dependencies {
api(project(":spring-core"))
optional("org.jetbrains.kotlin:kotlin-reflect")
testImplementation(testFixtures(project(":spring-core")))
testImplementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.jetbrains.kotlin:kotlin-stdlib")

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

@ -27,7 +27,15 @@ import java.util.Map; @@ -27,7 +27,15 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass;
import kotlin.reflect.KMutableProperty;
import kotlin.reflect.KProperty;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.ReflectJvmMapping;
import org.springframework.asm.MethodVisitor;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.Property;
import org.springframework.core.convert.TypeDescriptor;
@ -55,6 +63,7 @@ import org.springframework.util.StringUtils; @@ -55,6 +63,7 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller
* @author Phillip Webb
* @author Sam Brannen
* @author Sebastien Deleuze
* @since 3.0
* @see StandardEvaluationContext
* @see SimpleEvaluationContext
@ -401,7 +410,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -401,7 +410,8 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
Method[] methods = getSortedMethods(clazz);
for (String methodSuffix : methodSuffixes) {
for (Method method : methods) {
if (isCandidateForProperty(method, clazz) && method.getName().equals(prefix + methodSuffix) &&
if (isCandidateForProperty(method, clazz) &&
(method.getName().equals(prefix + methodSuffix) || isKotlinProperty(method, methodSuffix)) &&
method.getParameterCount() == numberOfParams &&
(!mustBeStatic || Modifier.isStatic(method.getModifiers())) &&
(requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) {
@ -557,6 +567,13 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -557,6 +567,13 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return this;
}
private static boolean isKotlinProperty(Method method, String methodSuffix) {
Class<?> clazz = method.getDeclaringClass();
return KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(clazz) &&
KotlinDelegate.isKotlinProperty(method, methodSuffix);
}
/**
* Captures the member (method/field) to call reflectively to access a property value
@ -755,4 +772,24 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -755,4 +772,24 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
}
}
/**
* Inner class to avoid a hard dependency on Kotlin at runtime.
*/
private static class KotlinDelegate {
public static boolean isKotlinProperty(Method method, String methodSuffix) {
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
for (KProperty<?> property : KClasses.getMemberProperties(kClass)) {
if (methodSuffix.equalsIgnoreCase(property.getName()) &&
(method.equals(ReflectJvmMapping.getJavaGetter(property)) ||
property instanceof KMutableProperty<?> mutableProperty &&
method.equals(ReflectJvmMapping.getJavaSetter(mutableProperty)))) {
return true;
}
}
return false;
}
}
}

21
spring-expression/src/test/kotlin/org/springframework/expression/spel/SpelReproKotlinTests.kt

@ -55,6 +55,20 @@ class SpelReproKotlinTests { @@ -55,6 +55,20 @@ class SpelReproKotlinTests {
assertThat(expr.getValue(context, Boolean::class.java)).isFalse()
}
@Test
fun `gh-30468 Unmangle Kotlin inlined class getter`() {
context.setVariable("something", Something(UUID(123), "name"))
val expr = parser.parseExpression("#something.id")
assertThat(expr.getValue(context, Int::class.java)).isEqualTo(123)
}
@Test
fun `gh-30468 Unmangle Kotlin inlined class setter`() {
context.setVariable("something", Something(UUID(123), "name"))
val expr = parser.parseExpression("#something.id = 456")
assertThat(expr.getValue(context, Int::class.java)).isEqualTo(456)
}
@Suppress("UNUSED_PARAMETER")
class Config {
@ -71,4 +85,11 @@ class SpelReproKotlinTests { @@ -71,4 +85,11 @@ class SpelReproKotlinTests {
}
}
@JvmInline value class UUID(val value: Int)
data class Something(
var id: UUID,
var name: String,
)
}

Loading…
Cancel
Save