diff --git a/pom.xml b/pom.xml
index f8041b0c90..3d928d5bff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 4.0.0-SNAPSHOT
+ 4.0.x-GH-3387-SNAPSHOT
Spring Data Core
Core Spring concepts underpinning every Spring Data module.
diff --git a/src/main/java/org/springframework/data/aot/DefaultAotContext.java b/src/main/java/org/springframework/data/aot/DefaultAotContext.java
index 9231471213..ff4d797d17 100644
--- a/src/main/java/org/springframework/data/aot/DefaultAotContext.java
+++ b/src/main/java/org/springframework/data/aot/DefaultAotContext.java
@@ -64,7 +64,7 @@ class DefaultAotContext implements AotContext {
private final Map, ContextualTypeConfiguration> typeConfigurations = new HashMap<>();
private final Environment environment;
- private final ReflectiveRuntimeHintsRegistrar runtimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar();
+ private final ReflectiveRuntimeHintsRegistrar reflectiveRuntimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar();
public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
this(beanFactory, environment, new AotMappingContext());
@@ -250,6 +250,9 @@ private void doContribute(Environment environment, GenerationContext generationC
categories.toArray(MemberCategory[]::new));
}
+ // check types for presence of @Reflective annotation
+ reflectiveRuntimeHintsRegistrar.registerRuntimeHints(generationContext.getRuntimeHints(), type);
+
if (contributeAccessors) {
AccessorContributionConfiguration configuration = AccessorContributionConfiguration.of(environment);
@@ -259,7 +262,6 @@ private void doContribute(Environment environment, GenerationContext generationC
}
if (forDataBinding) {
- runtimeHintsRegistrar.registerRuntimeHints(generationContext.getRuntimeHints(), type);
TypeContributor.contribute(type, Set.of(TypeContributor.DATA_NAMESPACE), generationContext);
}
diff --git a/src/main/java/org/springframework/data/util/TypeContributor.java b/src/main/java/org/springframework/data/util/TypeContributor.java
index c7781dd7ac..98c61bc7d7 100644
--- a/src/main/java/org/springframework/data/util/TypeContributor.java
+++ b/src/main/java/org/springframework/data/util/TypeContributor.java
@@ -22,7 +22,7 @@
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
-import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.core.annotation.MergedAnnotation;
/**
@@ -32,7 +32,8 @@
public class TypeContributor {
public static final String DATA_NAMESPACE = "org.springframework.data";
- public static final BindingReflectionHintsRegistrar REGISTRAR = new BindingReflectionHintsRegistrar();
+ public static final BindingReflectionHintsRegistrar DATA_BINDING_REGISTRAR = new BindingReflectionHintsRegistrar();
+ public static final ReflectiveRuntimeHintsRegistrar REFLECTIVE_REGISTRAR = new ReflectiveRuntimeHintsRegistrar();
/**
* Contribute the type with default reflection configuration, skip annotations.
@@ -67,7 +68,8 @@ public static void contribute(Class> type, Predicate {
+ // no specific action
+ });
+
+ TestGenerationContext generationContext = new TestGenerationContext();
+ context.contributeTypeConfigurations(generationContext);
+
+ assertThat(generationContext.getRuntimeHints()).matches(reflection().onType(Dummy.class).negate());
+ }
+
+ @Test // GH-3387
+ void doesNotRegisterReflectionWithCategoryAccordingly() {
+
+ DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
+ context.typeConfiguration(Dummy.class, it -> it.forReflectiveAccess(MemberCategory.ACCESS_DECLARED_FIELDS));
+
+ TestGenerationContext generationContext = new TestGenerationContext();
+ context.contributeTypeConfigurations(generationContext);
+
+ assertThat(generationContext.getRuntimeHints())
+ .matches(reflection().onType(Dummy.class).withAnyMemberCategory(MemberCategory.ACCESS_DECLARED_FIELDS));
+ }
+
+ @Test // GH-3387
+ void registerReflectionIfThereIsAnAtReflectiveAnnotation() throws NoSuchMethodException {
+
+ DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
+ context.typeConfiguration(DummyWithAtReflective.class, it -> {
+
+ });
+
+ TestGenerationContext generationContext = new TestGenerationContext();
+ context.contributeTypeConfigurations(generationContext);
+
+ assertThat(generationContext.getRuntimeHints())
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("reflectiveAnnotated")))
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("getValue")).negate())
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("justAMethod")).negate());
+ }
+
+ @Test // GH-3387
+ void registerReflectionForGetterSetterIfDataBindingRequested() throws NoSuchMethodException {
+
+ DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
+ context.typeConfiguration(DummyWithAtReflective.class, AotTypeConfiguration::forDataBinding);
+
+ TestGenerationContext generationContext = new TestGenerationContext();
+ context.contributeTypeConfigurations(generationContext);
+
+ assertThat(generationContext.getRuntimeHints())
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("reflectiveAnnotated")))
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("getValue")))
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("justAMethod")).negate());
+ }
+
+ @Test // GH-3387
+ void registerReflectionIfThereIsAnAtReflectiveAnnotationInTheSuperType() throws NoSuchMethodException {
+ DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
+ context.typeConfiguration(ExtendingDummyWithAtReflective.class, it -> {
+
+ });
+
+ TestGenerationContext generationContext = new TestGenerationContext();
+ context.contributeTypeConfigurations(generationContext);
+
+ assertThat(generationContext.getRuntimeHints())
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("reflectiveAnnotated")))
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("justAMethod")).negate())
+ .matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("getValue")).negate());
+ }
+
+ static class Dummy {
+
+ String value;
+
+ public List justAMethod() {
+ return null;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+ }
+
+ static class DummyWithAtReflective extends Dummy {
+
+ @Reflective
+ public List reflectiveAnnotated() {
+ return null;
+ }
+ }
+
+ static class ExtendingDummyWithAtReflective extends DummyWithAtReflective {}
+
+}