From 807325916f8ee60414a6160b6258c47de24411a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Fri, 31 Mar 2023 10:38:27 +0200 Subject: [PATCH] Add class hints for Jackson annotations on fields and methods Before this commit, only class level annotations were processed. Closes gh-30208 --- .../hint/BindingReflectionHintsRegistrar.java | 16 +++++--- .../BindingReflectionHintsRegistrarTests.java | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) 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 41e10ea576fe..806229cb4ed5 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 @@ -169,6 +169,7 @@ private void registerJacksonHints(ReflectionHints hints, Class clazz) { if (sourceField != null) { hints.registerField(sourceField); } + registerHintsForClassAttributes(hints, annotation); })); ReflectionUtils.doWithMethods(clazz, method -> forEachJacksonAnnotation(method, annotation -> { @@ -176,12 +177,9 @@ private void registerJacksonHints(ReflectionHints hints, Class clazz) { if (sourceMethod != null) { hints.registerMethod(sourceMethod, ExecutableMode.INVOKE); } + registerHintsForClassAttributes(hints, annotation); })); - forEachJacksonAnnotation(clazz, annotation -> annotation.getRoot().asMap().values().forEach(value -> { - if (value instanceof Class classValue) { - hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); - } - })); + forEachJacksonAnnotation(clazz, annotation -> registerHintsForClassAttributes(hints, annotation)); } private void forEachJacksonAnnotation(AnnotatedElement element, Consumer> action) { @@ -192,6 +190,14 @@ private void forEachJacksonAnnotation(AnnotatedElement element, Consumer annotation) { + annotation.getRoot().asMap().values().forEach(value -> { + if (value instanceof Class classValue && value != Void.class) { + hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + } + }); + } + /** * 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 f8d5e5622fe0..73327b3b40aa 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 @@ -16,15 +16,21 @@ package org.springframework.aot.hint; +import java.io.IOException; import java.lang.reflect.Type; +import java.time.LocalDate; import java.util.List; import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; 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 com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; @@ -265,6 +271,15 @@ void registerTypeForJacksonCustomStrategy() { .accepts(this.hints); } + @Test + void registerTypeForAnnotationOnMethodAndField() { + bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleClassWithJsonProperty.class); + assertThat(RuntimeHintsPredicates.reflection().onType(CustomDeserializer1.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) + .accepts(this.hints); + assertThat(RuntimeHintsPredicates.reflection().onType(CustomDeserializer2.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) + .accepts(this.hints); + } + static class SampleEmptyClass { } @@ -359,9 +374,11 @@ public String getNameProperty() { static class SampleClassWithJsonProperty { @JsonProperty + @JsonDeserialize(using = CustomDeserializer1.class) private String privateField = ""; @JsonProperty + @JsonDeserialize(using = CustomDeserializer2.class) String packagePrivateMethod() { return ""; } @@ -393,4 +410,28 @@ public SampleRecordWithJacksonCustomStrategy build() { } + static class CustomDeserializer1 extends StdDeserializer { + + public CustomDeserializer1() { + super(CustomDeserializer1.class); + } + + @Override + public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + return null; + } + } + + static class CustomDeserializer2 extends StdDeserializer { + + public CustomDeserializer2() { + super(CustomDeserializer2.class); + } + + @Override + public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + return null; + } + } + }