Skip to content

Commit e6397c8

Browse files
committed
Infer reflection hints for Jackson annotation class attributes
Closes gh-29646 Closes gh-29386
1 parent 1e47f31 commit e6397c8

File tree

3 files changed

+56
-11
lines changed

3 files changed

+56
-11
lines changed

spring-core/spring-core.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ dependencies {
8585
testImplementation("org.skyscreamer:jsonassert")
8686
testImplementation("com.squareup.okhttp3:mockwebserver")
8787
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
88-
testImplementation("com.fasterxml.jackson.core:jackson-annotations")
88+
testImplementation("com.fasterxml.jackson.core:jackson-databind")
8989
testFixturesImplementation("com.google.code.findbugs:jsr305")
9090
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
9191
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")

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

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package org.springframework.aot.hint;
1818

19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.AnnotatedElement;
1921
import java.lang.reflect.Field;
2022
import java.lang.reflect.Method;
2123
import java.lang.reflect.RecordComponent;
2224
import java.lang.reflect.Type;
2325
import java.util.LinkedHashSet;
2426
import java.util.Set;
27+
import java.util.function.Consumer;
2528

2629
import kotlin.jvm.JvmClassMappingKt;
2730
import kotlin.reflect.KClass;
@@ -161,27 +164,32 @@ private void collectReferencedTypes(Set<Class<?>> types, ResolvableType resolvab
161164

162165
private void registerJacksonHints(ReflectionHints hints, Class<?> clazz) {
163166
ReflectionUtils.doWithFields(clazz, field ->
164-
MergedAnnotations
165-
.from(field, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
166-
.stream(JACKSON_ANNOTATION)
167-
.filter(MergedAnnotation::isMetaPresent)
168-
.forEach(annotation -> {
167+
forEachJacksonAnnotation(field, annotation -> {
169168
Field sourceField = (Field) annotation.getSource();
170169
if (sourceField != null) {
171170
hints.registerField(sourceField);
172171
}
173172
}));
174173
ReflectionUtils.doWithMethods(clazz, method ->
175-
MergedAnnotations
176-
.from(method, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
177-
.stream(JACKSON_ANNOTATION)
178-
.filter(MergedAnnotation::isMetaPresent)
179-
.forEach(annotation -> {
174+
forEachJacksonAnnotation(method, annotation -> {
180175
Method sourceMethod = (Method) annotation.getSource();
181176
if (sourceMethod != null) {
182177
hints.registerMethod(sourceMethod, ExecutableMode.INVOKE);
183178
}
184179
}));
180+
forEachJacksonAnnotation(clazz, annotation -> annotation.getRoot().asMap().values().forEach(value -> {
181+
if (value instanceof Class<?> classValue) {
182+
hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
183+
}
184+
}));
185+
}
186+
187+
private void forEachJacksonAnnotation(AnnotatedElement element, Consumer<MergedAnnotation<Annotation>> action) {
188+
MergedAnnotations
189+
.from(element, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
190+
.stream(JACKSON_ANNOTATION)
191+
.filter(MergedAnnotation::isMetaPresent)
192+
.forEach(action::accept);
185193
}
186194

187195
/**

spring-core/src/test/java/org/springframework/aot/hint/BindingReflectionHintsRegistrarTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
import java.util.Set;
2222

2323
import com.fasterxml.jackson.annotation.JsonProperty;
24+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
25+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
26+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
27+
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
2428
import org.junit.jupiter.api.Test;
2529

2630
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
@@ -252,6 +256,15 @@ void registerTypeForInheritedJacksonAnnotations() {
252256
.accepts(this.hints);
253257
}
254258

259+
@Test
260+
void registerTypeForJacksonCustomStrategy() {
261+
bindingRegistrar.registerReflectionHints(this.hints.reflection(), SampleRecordWithJacksonCustomStrategy.class);
262+
assertThat(RuntimeHintsPredicates.reflection().onType(PropertyNamingStrategies.UpperSnakeCaseStrategy.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
263+
.accepts(this.hints);
264+
assertThat(RuntimeHintsPredicates.reflection().onType(SampleRecordWithJacksonCustomStrategy.Builder.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))
265+
.accepts(this.hints);
266+
}
267+
255268

256269
static class SampleEmptyClass {
257270
}
@@ -356,4 +369,28 @@ String packagePrivateMethod() {
356369

357370
static class SampleClassWithInheritedJsonProperty extends SampleClassWithJsonProperty {}
358371

372+
@JsonNaming(PropertyNamingStrategies.UpperSnakeCaseStrategy.class)
373+
@JsonDeserialize(builder = SampleRecordWithJacksonCustomStrategy.Builder.class)
374+
record SampleRecordWithJacksonCustomStrategy(String name) {
375+
376+
@JsonPOJOBuilder(withPrefix = "")
377+
public static class Builder {
378+
private String name;
379+
380+
public static Builder newInstance() {
381+
return new Builder();
382+
}
383+
384+
public Builder id(String name) {
385+
this.name = name;
386+
return this;
387+
}
388+
389+
public SampleRecordWithJacksonCustomStrategy build() {
390+
return new SampleRecordWithJacksonCustomStrategy(name);
391+
}
392+
}
393+
394+
}
395+
359396
}

0 commit comments

Comments
 (0)