Add support for Jackson annotations in BindingReflectionHintsRegistrar

This commits registers reflection hints on field and methods
where Jackson annotations are detected.

Closes gh-29426
This commit is contained in:
Sébastien Deleuze 2022-11-08 16:19:06 +01:00
parent 2878ade980
commit 60c9f2f72f
3 changed files with 73 additions and 2 deletions

View File

@ -84,6 +84,8 @@ dependencies {
testImplementation("io.projectreactor.tools:blockhound") testImplementation("io.projectreactor.tools:blockhound")
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("com.fasterxml.jackson.core:jackson-annotations")
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")
@ -91,7 +93,6 @@ dependencies {
testFixturesImplementation("org.assertj:assertj-core") testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("org.xmlunit:xmlunit-assertj") testFixturesImplementation("org.xmlunit:xmlunit-assertj")
testFixturesImplementation("io.projectreactor:reactor-test") testFixturesImplementation("io.projectreactor:reactor-test")
testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json")
} }
jar { jar {

View File

@ -16,6 +16,7 @@
package org.springframework.aot.hint; package org.springframework.aot.hint;
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;
@ -28,8 +29,11 @@ import kotlin.reflect.KClass;
import org.springframework.core.KotlinDetector; import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType; 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.lang.Nullable;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/** /**
* Register the necessary reflection hints so that the specified type can be * 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 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. * Register the necessary reflection hints to bind the specified types.
* @param hints the hints instance to use * @param hints the hints instance to use
@ -97,6 +106,9 @@ public class BindingReflectionHintsRegistrar {
} }
} }
} }
if (jacksonAnnotationPresent) {
registerJacksonHints(hints, clazz);
}
} }
if (KotlinDetector.isKotlinType(clazz)) { if (KotlinDetector.isKotlinType(clazz)) {
KotlinDelegate.registerComponentHints(hints, 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. * Inner class to avoid a hard dependency on Kotlin at runtime.
*/ */

View File

@ -20,8 +20,10 @@ import java.lang.reflect.Type;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import static org.assertj.core.api.Assertions.assertThat; 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 { static class SampleEmptyClass {
} }
@ -291,7 +311,7 @@ public class BindingReflectionHintsRegistrarTests {
} }
} }
class SampleClassC { static class SampleClassC {
public String getString() { public String getString() {
return ""; return "";
} }
@ -303,4 +323,17 @@ public class BindingReflectionHintsRegistrarTests {
record SampleRecord(String name) {} record SampleRecord(String name) {}
static class SampleClassWithJsonProperty {
@JsonProperty
private String privateField = "";
@JsonProperty
String packagePrivateMethod() {
return "";
}
}
static class SampleClassWithInheritedJsonProperty extends SampleClassWithJsonProperty {}
} }