From 60c9f2f72f6a30c8fdedb295c381291735d203a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Tue, 8 Nov 2022 16:19:06 +0100 Subject: [PATCH] Add support for Jackson annotations in BindingReflectionHintsRegistrar This commits registers reflection hints on field and methods where Jackson annotations are detected. Closes gh-29426 --- spring-core/spring-core.gradle | 3 +- .../hint/BindingReflectionHintsRegistrar.java | 37 +++++++++++++++++++ .../BindingReflectionHintsRegistrarTests.java | 35 +++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 9c9f4a04a87..1948a728f6a 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -84,6 +84,8 @@ dependencies { testImplementation("io.projectreactor.tools:blockhound") testImplementation("org.skyscreamer:jsonassert") testImplementation("com.squareup.okhttp3:mockwebserver") + testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") + testImplementation("com.fasterxml.jackson.core:jackson-annotations") testFixturesImplementation("com.google.code.findbugs:jsr305") testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") @@ -91,7 +93,6 @@ dependencies { testFixturesImplementation("org.assertj:assertj-core") testFixturesImplementation("org.xmlunit:xmlunit-assertj") testFixturesImplementation("io.projectreactor:reactor-test") - testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") } jar { diff --git a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java index c162b0b4514..7433cbbfec5 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java @@ -16,6 +16,7 @@ package org.springframework.aot.hint; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; @@ -28,8 +29,11 @@ import kotlin.reflect.KClass; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; /** * Register the necessary reflection hints so that the specified type can be @@ -45,6 +49,11 @@ public class BindingReflectionHintsRegistrar { private static final String KOTLIN_COMPANION_SUFFIX = "$Companion"; + private static final String JACKSON_ANNOTATION = "com.fasterxml.jackson.annotation.JacksonAnnotation"; + + private static final boolean jacksonAnnotationPresent = ClassUtils.isPresent(JACKSON_ANNOTATION, + BindingReflectionHintsRegistrar.class.getClassLoader()); + /** * Register the necessary reflection hints to bind the specified types. * @param hints the hints instance to use @@ -97,6 +106,9 @@ public class BindingReflectionHintsRegistrar { } } } + if (jacksonAnnotationPresent) { + registerJacksonHints(hints, clazz); + } } if (KotlinDetector.isKotlinType(clazz)) { KotlinDelegate.registerComponentHints(hints, clazz); @@ -147,6 +159,31 @@ public class BindingReflectionHintsRegistrar { } } + private void registerJacksonHints(ReflectionHints hints, Class clazz) { + ReflectionUtils.doWithFields(clazz, field -> + MergedAnnotations + .from(field, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(JACKSON_ANNOTATION) + .filter(MergedAnnotation::isMetaPresent) + .forEach(annotation -> { + Field sourceField = (Field) annotation.getSource(); + if (sourceField != null) { + hints.registerField(sourceField); + } + })); + ReflectionUtils.doWithMethods(clazz, method -> + MergedAnnotations + .from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(JACKSON_ANNOTATION) + .filter(MergedAnnotation::isMetaPresent) + .forEach(annotation -> { + Method sourceMethod = (Method) annotation.getSource(); + if (sourceMethod != null) { + hints.registerMethod(sourceMethod, ExecutableMode.INVOKE); + } + })); + } + /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ diff --git a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java index 266738136d7..4bd9ced5f48 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java @@ -20,8 +20,10 @@ import java.lang.reflect.Type; import java.util.List; import java.util.Set; +import com.fasterxml.jackson.annotation.JsonProperty; import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.core.ResolvableType; import static org.assertj.core.api.Assertions.assertThat; @@ -219,6 +221,24 @@ public class BindingReflectionHintsRegistrarTests { }); } + @Test + void registerTypeForJacksonAnnotations() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithJsonProperty.class); + assertThat(RuntimeHintsPredicates.reflection().onField(SampleClassWithJsonProperty.class, "privateField")) + .accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleClassWithJsonProperty.class, "packagePrivateMethod").invoke()) + .accepts(this.hints); + } + + @Test + void registerTypeForInheritedJacksonAnnotations() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithInheritedJsonProperty.class); + assertThat(RuntimeHintsPredicates.reflection().onField(SampleClassWithJsonProperty.class, "privateField")) + .accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleClassWithJsonProperty.class, "packagePrivateMethod").invoke()) + .accepts(this.hints); + } + static class SampleEmptyClass { } @@ -291,7 +311,7 @@ public class BindingReflectionHintsRegistrarTests { } } - class SampleClassC { + static class SampleClassC { public String getString() { return ""; } @@ -303,4 +323,17 @@ public class BindingReflectionHintsRegistrarTests { record SampleRecord(String name) {} + static class SampleClassWithJsonProperty { + + @JsonProperty + private String privateField = ""; + + @JsonProperty + String packagePrivateMethod() { + return ""; + } + } + + static class SampleClassWithInheritedJsonProperty extends SampleClassWithJsonProperty {} + }