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
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
* @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 {
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmInline value class UUID(val value: Int)
|
||||
|
||||
data class Something(
|
||||
var id: UUID,
|
||||
var name: String,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user