Skip to content

Commit e02705f

Browse files
Register native hints for (at)Reflective annotated methods of domain types.
1 parent 2d0dc33 commit e02705f

File tree

3 files changed

+157
-5
lines changed

3 files changed

+157
-5
lines changed

src/main/java/org/springframework/data/aot/DefaultAotContext.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class DefaultAotContext implements AotContext {
6464

6565
private final Map<Class<?>, ContextualTypeConfiguration> typeConfigurations = new HashMap<>();
6666
private final Environment environment;
67-
private final ReflectiveRuntimeHintsRegistrar runtimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar();
67+
private final ReflectiveRuntimeHintsRegistrar reflectiveRuntimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar();
6868

6969
public DefaultAotContext(BeanFactory beanFactory, Environment environment) {
7070
this(beanFactory, environment, new AotMappingContext());
@@ -250,6 +250,9 @@ private void doContribute(Environment environment, GenerationContext generationC
250250
categories.toArray(MemberCategory[]::new));
251251
}
252252

253+
// check types for presence of @Reflective annotation
254+
reflectiveRuntimeHintsRegistrar.registerRuntimeHints(generationContext.getRuntimeHints(), type);
255+
253256
if (contributeAccessors) {
254257

255258
AccessorContributionConfiguration configuration = AccessorContributionConfiguration.of(environment);
@@ -259,7 +262,6 @@ private void doContribute(Environment environment, GenerationContext generationC
259262
}
260263

261264
if (forDataBinding) {
262-
runtimeHintsRegistrar.registerRuntimeHints(generationContext.getRuntimeHints(), type);
263265
TypeContributor.contribute(type, Set.of(TypeContributor.DATA_NAMESPACE), generationContext);
264266
}
265267

src/main/java/org/springframework/data/util/TypeContributor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import org.springframework.aot.generate.GenerationContext;
2424
import org.springframework.aot.hint.BindingReflectionHintsRegistrar;
25-
import org.springframework.aot.hint.MemberCategory;
25+
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
2626
import org.springframework.core.annotation.MergedAnnotation;
2727

2828
/**
@@ -32,7 +32,8 @@
3232
public class TypeContributor {
3333

3434
public static final String DATA_NAMESPACE = "org.springframework.data";
35-
public static final BindingReflectionHintsRegistrar REGISTRAR = new BindingReflectionHintsRegistrar();
35+
public static final BindingReflectionHintsRegistrar DATA_BINDING_REGISTRAR = new BindingReflectionHintsRegistrar();
36+
public static final ReflectiveRuntimeHintsRegistrar REFLECTIVE_REGISTRAR = new ReflectiveRuntimeHintsRegistrar();
3637

3738
/**
3839
* Contribute the type with default reflection configuration, skip annotations.
@@ -67,7 +68,8 @@ public static void contribute(Class<?> type, Predicate<Class<? extends Annotatio
6768
return;
6869
}
6970

70-
REGISTRAR.registerReflectionHints(contribution.getRuntimeHints().reflection(), type);
71+
DATA_BINDING_REGISTRAR.registerReflectionHints(contribution.getRuntimeHints().reflection(), type);
72+
REFLECTIVE_REGISTRAR.registerRuntimeHints(contribution.getRuntimeHints(), type);
7173
}
7274

7375
/**
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.aot;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
20+
21+
import java.util.List;
22+
23+
import org.junit.jupiter.api.Test;
24+
import org.mockito.Mock;
25+
import org.mockito.junit.jupiter.MockitoSettings;
26+
import org.springframework.aot.hint.MemberCategory;
27+
import org.springframework.aot.hint.annotation.Reflective;
28+
import org.springframework.aot.test.generate.TestGenerationContext;
29+
import org.springframework.beans.factory.BeanFactory;
30+
import org.springframework.mock.env.MockEnvironment;
31+
32+
/**
33+
* Unit tests targeting {@link DefaultAotContext};
34+
*
35+
* @author Christoph Strobl
36+
*/
37+
@MockitoSettings(strictness = org.mockito.quality.Strictness.LENIENT)
38+
public class DefaultAotContextUnitTests {
39+
40+
@Mock BeanFactory beanFactory;
41+
42+
@Mock AotMappingContext mappingContext;
43+
44+
MockEnvironment mockEnvironment = new MockEnvironment();
45+
46+
@Test // GH-3387
47+
void doesNotRegisterReflectionWhenThereIsNothingToRegister() {
48+
49+
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
50+
context.typeConfiguration(Dummy.class, it -> {
51+
// no specific action
52+
});
53+
54+
TestGenerationContext generationContext = new TestGenerationContext();
55+
context.contributeTypeConfigurations(generationContext);
56+
57+
assertThat(generationContext.getRuntimeHints()).matches(reflection().onType(Dummy.class).negate());
58+
}
59+
60+
@Test // GH-3387
61+
void doesNotRegisterReflectionWithCategoryAccordingly() {
62+
63+
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
64+
context.typeConfiguration(Dummy.class, it -> it.forReflectiveAccess(MemberCategory.ACCESS_DECLARED_FIELDS));
65+
66+
TestGenerationContext generationContext = new TestGenerationContext();
67+
context.contributeTypeConfigurations(generationContext);
68+
69+
assertThat(generationContext.getRuntimeHints())
70+
.matches(reflection().onType(Dummy.class).withAnyMemberCategory(MemberCategory.ACCESS_DECLARED_FIELDS));
71+
}
72+
73+
@Test // GH-3387
74+
void registerReflectionIfThereIsAnAtReflectiveAnnotation() throws NoSuchMethodException {
75+
76+
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
77+
context.typeConfiguration(DummyWithAtReflective.class, it -> {
78+
79+
});
80+
81+
TestGenerationContext generationContext = new TestGenerationContext();
82+
context.contributeTypeConfigurations(generationContext);
83+
84+
assertThat(generationContext.getRuntimeHints())
85+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("reflectiveAnnotated")))
86+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("getValue")).negate())
87+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("justAMethod")).negate());
88+
}
89+
90+
@Test // GH-3387
91+
void registerReflectionForGetterSetterIfDataBindingRequested() throws NoSuchMethodException {
92+
93+
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
94+
context.typeConfiguration(DummyWithAtReflective.class, AotTypeConfiguration::forDataBinding);
95+
96+
TestGenerationContext generationContext = new TestGenerationContext();
97+
context.contributeTypeConfigurations(generationContext);
98+
99+
assertThat(generationContext.getRuntimeHints())
100+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("reflectiveAnnotated")))
101+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("getValue")))
102+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("justAMethod")).negate());
103+
}
104+
105+
@Test // GH-3387
106+
void registerReflectionIfThereIsAnAtReflectiveAnnotationInTheSuperType() throws NoSuchMethodException {
107+
DefaultAotContext context = new DefaultAotContext(beanFactory, mockEnvironment, mappingContext);
108+
context.typeConfiguration(ExtendingDummyWithAtReflective.class, it -> {
109+
110+
});
111+
112+
TestGenerationContext generationContext = new TestGenerationContext();
113+
context.contributeTypeConfigurations(generationContext);
114+
115+
assertThat(generationContext.getRuntimeHints())
116+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("reflectiveAnnotated")))
117+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("justAMethod")).negate())
118+
.matches(reflection().onMethodInvocation(DummyWithAtReflective.class.getMethod("getValue")).negate());
119+
}
120+
121+
static class Dummy {
122+
123+
String value;
124+
125+
public List<String> justAMethod() {
126+
return null;
127+
}
128+
129+
public String getValue() {
130+
return value;
131+
}
132+
133+
public void setValue(String value) {
134+
this.value = value;
135+
}
136+
}
137+
138+
static class DummyWithAtReflective extends Dummy {
139+
140+
@Reflective
141+
public List<String> reflectiveAnnotated() {
142+
return null;
143+
}
144+
}
145+
146+
static class ExtendingDummyWithAtReflective extends DummyWithAtReflective {}
147+
148+
}

0 commit comments

Comments
 (0)