From e6397c8a38696d7da58401eecea515f0cd986554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 23 Jan 2023 13:21:34 +0100 Subject: [PATCH] Infer reflection hints for Jackson annotation class attributes Closes gh-29646 Closes gh-29386 --- spring-core/spring-core.gradle | 2 +- .../hint/BindingReflectionHintsRegistrar.java | 28 +++++++++----- .../BindingReflectionHintsRegistrarTests.java | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 1948a728f6a..086977e8c11 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -85,7 +85,7 @@ dependencies { testImplementation("org.skyscreamer:jsonassert") testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") - testImplementation("com.fasterxml.jackson.core:jackson-annotations") + testImplementation("com.fasterxml.jackson.core:jackson-databind") testFixturesImplementation("com.google.code.findbugs:jsr305") testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") 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 4e3d0f4136a..c91325eeef9 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,12 +16,15 @@ package org.springframework.aot.hint; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; import java.util.LinkedHashSet; import java.util.Set; +import java.util.function.Consumer; import kotlin.jvm.JvmClassMappingKt; import kotlin.reflect.KClass; @@ -161,27 +164,32 @@ 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 -> { + forEachJacksonAnnotation(field, 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 -> { + forEachJacksonAnnotation(method, annotation -> { Method sourceMethod = (Method) annotation.getSource(); if (sourceMethod != null) { hints.registerMethod(sourceMethod, ExecutableMode.INVOKE); } })); + forEachJacksonAnnotation(clazz, annotation -> annotation.getRoot().asMap().values().forEach(value -> { + if (value instanceof Class classValue) { + hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + } + })); + } + + private void forEachJacksonAnnotation(AnnotatedElement element, Consumer> action) { + MergedAnnotations + .from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY) + .stream(JACKSON_ANNOTATION) + .filter(MergedAnnotation::isMetaPresent) + .forEach(action::accept); } /** 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 8341c91713f..f89855ee35a 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 @@ -21,6 +21,10 @@ import java.util.List; import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; @@ -252,6 +256,15 @@ public class BindingReflectionHintsRegistrarTests { .accepts(this.hints); } + @Test + void registerTypeForJacksonCustomStrategy() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleRecordWithJacksonCustomStrategy.class); + assertThat(RuntimeHintsPredicates.reflection().onType(PropertyNamingStrategies.UpperSnakeCaseStrategy.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) + .accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onType(SampleRecordWithJacksonCustomStrategy.Builder.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) + .accepts(this.hints); + } + static class SampleEmptyClass { } @@ -356,4 +369,28 @@ public class BindingReflectionHintsRegistrarTests { static class SampleClassWithInheritedJsonProperty extends SampleClassWithJsonProperty {} + @JsonNaming(PropertyNamingStrategies.UpperSnakeCaseStrategy.class) + @JsonDeserialize(builder = SampleRecordWithJacksonCustomStrategy.Builder.class) + record SampleRecordWithJacksonCustomStrategy(String name) { + + @JsonPOJOBuilder(withPrefix = "") + public static class Builder { + private String name; + + public static Builder newInstance() { + return new Builder(); + } + + public Builder id(String name) { + this.name = name; + return this; + } + + public SampleRecordWithJacksonCustomStrategy build() { + return new SampleRecordWithJacksonCustomStrategy(name); + } + } + + } + }