Skip to content

Commit 16d6dc3

Browse files
committed
Add Kotlinx Serialization support to BindingReflectionHintsRegistrar
Closes gh-28635
1 parent b5c0c6d commit 16d6dc3

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

spring-core/spring-core.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
33
description = "Spring Core"
44

55
apply plugin: "kotlin"
6+
apply plugin: "kotlinx-serialization"
67

78
// spring-core includes asm, javapoet and repackages cglib, inlining all into the
89
// spring-core jar. cglib itself depends on asm and is therefore further transformed by
@@ -68,6 +69,7 @@ dependencies {
6869
testImplementation("io.projectreactor:reactor-test")
6970
testImplementation("io.projectreactor.tools:blockhound")
7071
testImplementation("org.skyscreamer:jsonassert")
72+
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
7173
testFixturesImplementation("com.google.code.findbugs:jsr305")
7274
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
7375
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")

spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@
3535
import org.springframework.aot.hint.MemberCategory;
3636
import org.springframework.aot.hint.ReflectionHints;
3737
import org.springframework.aot.hint.TypeHint.Builder;
38+
import org.springframework.core.KotlinDetector;
3839
import org.springframework.core.MethodParameter;
3940
import org.springframework.core.ResolvableType;
41+
import org.springframework.util.ClassUtils;
4042

4143
/**
4244
* Register the necessary reflection hints so that the specified type can be
@@ -55,6 +57,8 @@ public class BindingReflectionHintsRegistrar {
5557
private static final Consumer<ExecutableHint.Builder> INVOKE = builder -> builder
5658
.withMode(ExecutableMode.INVOKE);
5759

60+
private static final String KOTLIN_COMPANION_SUFFIX = "$Companion";
61+
5862
/**
5963
* Register the necessary reflection hints to bind the specified types.
6064
* @param hints the hints instance to use
@@ -103,6 +107,14 @@ private void registerMembers(ReflectionHints hints, Class<?> type, Builder build
103107
registerReflectionHints(hints, methodParameter.getGenericParameterType());
104108
}
105109
}
110+
String companionClassName = type.getCanonicalName() + KOTLIN_COMPANION_SUFFIX;
111+
if (KotlinDetector.isKotlinType(type) && ClassUtils.isPresent(companionClassName, null)) {
112+
Class<?> companionClass = ClassUtils.resolveClassName(companionClassName, null);
113+
Method serializerMethod = ClassUtils.getMethodIfAvailable(companionClass, "serializer");
114+
if (serializerMethod != null) {
115+
hints.registerMethod(serializerMethod);
116+
}
117+
}
106118
}
107119
catch (IntrospectionException ex) {
108120
if (logger.isDebugEnabled()) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.hint.support
18+
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.assertj.core.api.ThrowingConsumer
21+
import org.junit.jupiter.api.Test
22+
import org.springframework.aot.hint.*
23+
24+
/**
25+
* Tests for Kotlin support in [BindingReflectionHintsRegistrar].
26+
*
27+
* @author Sebastien Deleuze
28+
*/
29+
class KotlinBindingReflectionHintsRegistrarTests {
30+
31+
private val bindingRegistrar = BindingReflectionHintsRegistrar()
32+
33+
private val hints = RuntimeHints()
34+
35+
@Test
36+
fun `Register type for Kotlinx serialization`() {
37+
bindingRegistrar.registerReflectionHints(hints.reflection(), SampleSerializableClass::class.java)
38+
assertThat(hints.reflection().typeHints()).satisfiesExactlyInAnyOrder(
39+
ThrowingConsumer { typeHint: TypeHint ->
40+
assertThat(typeHint.type).isEqualTo(TypeReference.of(String::class.java))
41+
assertThat(typeHint.memberCategories).isEmpty()
42+
assertThat(typeHint.constructors()).isEmpty()
43+
assertThat(typeHint.fields()).isEmpty()
44+
assertThat(typeHint.methods()).isEmpty()
45+
},
46+
ThrowingConsumer { typeHint: TypeHint ->
47+
assertThat(typeHint.type).isEqualTo(TypeReference.of(SampleSerializableClass::class.java))
48+
assertThat(typeHint.methods()).singleElement()
49+
.satisfies(ThrowingConsumer { methodHint: ExecutableHint ->
50+
assertThat(methodHint.name).isEqualTo("getName")
51+
assertThat(methodHint.modes)
52+
.containsOnly(ExecutableMode.INVOKE)
53+
})
54+
},
55+
ThrowingConsumer { typeHint: TypeHint ->
56+
assertThat(typeHint.type).isEqualTo(TypeReference.of(SampleSerializableClass::class.qualifiedName + "\$Companion"))
57+
assertThat(typeHint.methods()).singleElement()
58+
.satisfies(ThrowingConsumer { methodHint: ExecutableHint ->
59+
assertThat(methodHint.name).isEqualTo("serializer")
60+
assertThat(methodHint.modes).containsOnly(ExecutableMode.INVOKE)
61+
})
62+
})
63+
}
64+
}
65+
66+
@kotlinx.serialization.Serializable
67+
class SampleSerializableClass(val name: String)

0 commit comments

Comments
 (0)