Infer reflection hints for Jackson annotation class attributes

Closes gh-29646
Closes gh-29386
This commit is contained in:
Sébastien Deleuze 2023-01-23 13:21:34 +01:00
parent 1e47f31210
commit e6397c8a38
3 changed files with 56 additions and 11 deletions

View File

@ -85,7 +85,7 @@ dependencies {
testImplementation("org.skyscreamer:jsonassert") testImplementation("org.skyscreamer:jsonassert")
testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("com.squareup.okhttp3:mockwebserver")
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json") 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("com.google.code.findbugs:jsr305")
testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.platform:junit-platform-launcher")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")

View File

@ -16,12 +16,15 @@
package org.springframework.aot.hint; package org.springframework.aot.hint;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent; import java.lang.reflect.RecordComponent;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import kotlin.jvm.JvmClassMappingKt; import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KClass; import kotlin.reflect.KClass;
@ -161,27 +164,32 @@ public class BindingReflectionHintsRegistrar {
private void registerJacksonHints(ReflectionHints hints, Class<?> clazz) { private void registerJacksonHints(ReflectionHints hints, Class<?> clazz) {
ReflectionUtils.doWithFields(clazz, field -> ReflectionUtils.doWithFields(clazz, field ->
MergedAnnotations forEachJacksonAnnotation(field, annotation -> {
.from(field, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.stream(JACKSON_ANNOTATION)
.filter(MergedAnnotation::isMetaPresent)
.forEach(annotation -> {
Field sourceField = (Field) annotation.getSource(); Field sourceField = (Field) annotation.getSource();
if (sourceField != null) { if (sourceField != null) {
hints.registerField(sourceField); hints.registerField(sourceField);
} }
})); }));
ReflectionUtils.doWithMethods(clazz, method -> ReflectionUtils.doWithMethods(clazz, method ->
MergedAnnotations forEachJacksonAnnotation(method, annotation -> {
.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.stream(JACKSON_ANNOTATION)
.filter(MergedAnnotation::isMetaPresent)
.forEach(annotation -> {
Method sourceMethod = (Method) annotation.getSource(); Method sourceMethod = (Method) annotation.getSource();
if (sourceMethod != null) { if (sourceMethod != null) {
hints.registerMethod(sourceMethod, ExecutableMode.INVOKE); 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<MergedAnnotation<Annotation>> action) {
MergedAnnotations
.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.stream(JACKSON_ANNOTATION)
.filter(MergedAnnotation::isMetaPresent)
.forEach(action::accept);
} }
/** /**

View File

@ -21,6 +21,10 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty; 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.junit.jupiter.api.Test;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
@ -252,6 +256,15 @@ public class BindingReflectionHintsRegistrarTests {
.accepts(this.hints); .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 { static class SampleEmptyClass {
} }
@ -356,4 +369,28 @@ public class BindingReflectionHintsRegistrarTests {
static class SampleClassWithInheritedJsonProperty extends SampleClassWithJsonProperty {} 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);
}
}
}
} }