Introduce KotlinDetector#hasSerializableAnnotation

This commit introduces a KotlinDetector#hasSerializableAnnotation
utility method designed to detect types annotated with
`@Serializable` at type or generics level.

See gh-35761
This commit is contained in:
Sébastien Deleuze
2025-11-10 11:34:49 +01:00
parent 24590092ef
commit a68d60768e
2 changed files with 52 additions and 0 deletions
@@ -38,6 +38,8 @@ public abstract class KotlinDetector {
private static final @Nullable Class<? extends Annotation> KOTLIN_JVM_INLINE;
private static final @Nullable Class<? extends Annotation> KOTLIN_SERIALIZABLE;
private static final @Nullable Class<?> KOTLIN_COROUTINE_CONTINUATION;
// For ConstantFieldFeature compliance, otherwise could be deduced from kotlinMetadata
@@ -49,6 +51,7 @@ public abstract class KotlinDetector {
ClassLoader classLoader = KotlinDetector.class.getClassLoader();
Class<?> metadata = null;
Class<?> jvmInline = null;
Class<?> serializable = null;
Class<?> coroutineContinuation = null;
try {
metadata = ClassUtils.forName("kotlin.Metadata", classLoader);
@@ -58,6 +61,12 @@ public abstract class KotlinDetector {
catch (ClassNotFoundException ex) {
// JVM inline support not available
}
try {
serializable = ClassUtils.forName("kotlinx.serialization.Serializable", classLoader);
}
catch (ClassNotFoundException ex) {
// Kotlin Serialization not available
}
try {
coroutineContinuation = ClassUtils.forName("kotlin.coroutines.Continuation", classLoader);
}
@@ -72,6 +81,7 @@ public abstract class KotlinDetector {
KOTLIN_PRESENT = (KOTLIN_METADATA != null);
KOTLIN_REFLECT_PRESENT = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
KOTLIN_JVM_INLINE = (Class<? extends Annotation>) jvmInline;
KOTLIN_SERIALIZABLE = (Class<? extends Annotation>) serializable;
KOTLIN_COROUTINE_CONTINUATION = coroutineContinuation;
}
@@ -125,4 +135,26 @@ public abstract class KotlinDetector {
return (KOTLIN_JVM_INLINE != null && clazz.getDeclaredAnnotation(KOTLIN_JVM_INLINE) != null);
}
/**
* Determine whether the given {@code ResolvableType} is annotated with {@code @kotlinx.serialization.Serializable}
* at type or generics level.
* @since 7.0
*/
public static boolean hasSerializableAnnotation(ResolvableType type) {
Class<?> resolvedClass = type.resolve();
if (KOTLIN_SERIALIZABLE == null || resolvedClass == null) {
return false;
}
if (resolvedClass.isAnnotationPresent(KOTLIN_SERIALIZABLE)) {
return true;
}
@Nullable Class<?>[] resolvedGenerics = type.resolveGenerics();
for (Class<?> resolvedGeneric : resolvedGenerics) {
if (resolvedGeneric != null && resolvedGeneric.isAnnotationPresent(KOTLIN_SERIALIZABLE)) {
return true;
}
}
return false;
}
}
@@ -15,6 +15,7 @@
*/
package org.springframework.core
import kotlinx.serialization.Serializable
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
@@ -46,7 +47,26 @@ class KotlinDetectorTests {
Assertions.assertThat(KotlinDetector.isInlineClass(KotlinDetectorTests::class.java)).isFalse()
}
@Test
fun hasSerializableAnnotation() {
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.forClass(WithoutSerializable::class.java))).isFalse()
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.forClass(WithSerializable::class.java))).isTrue()
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.forClassWithGenerics(List::class.java, WithoutSerializable::class.java))).isFalse()
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.forClassWithGenerics(List::class.java, WithSerializable::class.java))).isTrue()
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.forClassWithGenerics(Map::class.java, String::class.java, WithoutSerializable::class.java))).isFalse()
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.forClassWithGenerics(Map::class.java, String::class.java, WithSerializable::class.java))).isTrue()
Assertions.assertThat(KotlinDetector.hasSerializableAnnotation(ResolvableType.NONE)).isFalse()
}
@JvmInline
value class ValueClass(val value: String)
data class WithoutSerializable(val value: String)
@Serializable
data class WithSerializable(val value: String)
}