Skip to content

Commit 93b340e

Browse files
committed
Add reflection hints for HttpEntity
For those used in Web controllers. Closes gh-28622
1 parent 789329f commit 93b340e

File tree

3 files changed

+110
-9
lines changed

3 files changed

+110
-9
lines changed

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.core.KotlinDetector;
3939
import org.springframework.core.MethodParameter;
4040
import org.springframework.core.ResolvableType;
41+
import org.springframework.lang.Nullable;
4142
import org.springframework.util.ClassUtils;
4243

4344
/**
@@ -123,8 +124,8 @@ private void registerMembers(ReflectionHints hints, Class<?> type, Builder build
123124
}
124125
}
125126

126-
private void collectReferencedTypes(Set<Type> seen, Set<Class<?>> types, Type type) {
127-
if (seen.contains(type)) {
127+
private void collectReferencedTypes(Set<Type> seen, Set<Class<?>> types, @Nullable Type type) {
128+
if (type == null || seen.contains(type)) {
128129
return;
129130
}
130131
seen.add(type);

spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java

+35-7
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,26 @@
1919
import java.lang.reflect.AnnotatedElement;
2020
import java.lang.reflect.Method;
2121
import java.lang.reflect.Parameter;
22+
import java.lang.reflect.Type;
2223

2324
import org.springframework.aot.hint.ExecutableMode;
2425
import org.springframework.aot.hint.ReflectionHints;
2526
import org.springframework.aot.hint.annotation.ReflectiveProcessor;
2627
import org.springframework.aot.hint.support.BindingReflectionHintsRegistrar;
2728
import org.springframework.core.MethodParameter;
2829
import org.springframework.core.annotation.AnnotatedElementUtils;
30+
import org.springframework.http.HttpEntity;
31+
import org.springframework.lang.Nullable;
2932

3033
/**
3134
* {@link ReflectiveProcessor} implementation for {@link RequestMapping}
3235
* annotated types. On top of registering reflection hints for invoking
33-
* the annotated method, this implementation handles return types annotated
34-
* with {@link ResponseBody} and parameters annotated with {@link RequestBody}
35-
* which are serialized as well.
36+
* the annotated method, this implementation handles:
37+
* <ul>
38+
* <li>Return types annotated with {@link ResponseBody}.</li>
39+
* <li>Parameters annotated with {@link RequestBody}.</li>
40+
* <li>{@link HttpEntity} return type and parameters.</li>
41+
* </ul>
3642
*
3743
* @author Stephane Nicoll
3844
* @author Sebastien Deleuze
@@ -45,29 +51,51 @@ class RequestMappingReflectiveProcessor implements ReflectiveProcessor {
4551
@Override
4652
public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) {
4753
if (element instanceof Class<?> type) {
48-
registerTypeHint(hints, type);
54+
registerTypeHints(hints, type);
4955
}
5056
else if (element instanceof Method method) {
51-
registerMethodHint(hints, method);
57+
registerMethodHints(hints, method);
5258
}
5359
}
5460

55-
protected void registerTypeHint(ReflectionHints hints, Class<?> type) {
61+
protected void registerTypeHints(ReflectionHints hints, Class<?> type) {
5662
hints.registerType(type, hint -> {});
5763
}
5864

59-
protected void registerMethodHint(ReflectionHints hints, Method method) {
65+
protected void registerMethodHints(ReflectionHints hints, Method method) {
66+
hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE));
67+
registerParameterHints(hints, method);
68+
registerReturnValueHints(hints, method);
69+
}
70+
71+
protected void registerParameterHints(ReflectionHints hints, Method method) {
6072
hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE));
6173
for (Parameter parameter : method.getParameters()) {
6274
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
6375
if (methodParameter.hasParameterAnnotation(RequestBody.class)) {
6476
this.bindingRegistrar.registerReflectionHints(hints, methodParameter.getGenericParameterType());
6577
}
78+
else if (HttpEntity.class.isAssignableFrom(methodParameter.getParameterType())) {
79+
this.bindingRegistrar.registerReflectionHints(hints, getHttpEntityType(methodParameter));
80+
}
6681
}
82+
}
83+
84+
protected void registerReturnValueHints(ReflectionHints hints, Method method) {
6785
MethodParameter returnType = MethodParameter.forExecutable(method, -1);
6886
if (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
6987
returnType.hasMethodAnnotation(ResponseBody.class)) {
7088
this.bindingRegistrar.registerReflectionHints(hints, returnType.getGenericParameterType());
7189
}
90+
else if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) {
91+
this.bindingRegistrar.registerReflectionHints(hints, getHttpEntityType(returnType));
92+
}
7293
}
94+
95+
@Nullable
96+
protected Type getHttpEntityType(MethodParameter parameter) {
97+
MethodParameter nestedParameter = parameter.nested();
98+
return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() ? null : nestedParameter.getNestedParameterType());
99+
}
100+
73101
}

spring-web/src/test/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessorTests.java

+72
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.aot.hint.MemberCategory;
2424
import org.springframework.aot.hint.ReflectionHints;
2525
import org.springframework.aot.hint.TypeReference;
26+
import org.springframework.http.HttpEntity;
2627

2728
import static org.assertj.core.api.Assertions.assertThat;
2829

@@ -112,6 +113,58 @@ void registerReflectiveHintsForClassWithMapping() {
112113
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleControllerWithClassMapping.class)));
113114
}
114115

116+
@Test
117+
void registerReflectiveHintsForMethodReturningHttpEntity() throws NoSuchMethodException {
118+
Method method = SampleController.class.getDeclaredMethod("getHttpEntity");
119+
processor.registerReflectionHints(hints, method);
120+
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
121+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
122+
typeHint -> {
123+
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Response.class));
124+
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
125+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
126+
MemberCategory.DECLARED_FIELDS);
127+
assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder(
128+
hint -> assertThat(hint.getName()).isEqualTo("getMessage"),
129+
hint -> assertThat(hint.getName()).isEqualTo("setMessage"));
130+
},
131+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
132+
}
133+
134+
@Test
135+
void registerReflectiveHintsForMethodReturningRawHttpEntity() throws NoSuchMethodException {
136+
Method method = SampleController.class.getDeclaredMethod("getRawHttpEntity");
137+
processor.registerReflectionHints(hints, method);
138+
assertThat(hints.typeHints()).singleElement().satisfies(
139+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)));
140+
}
141+
142+
@Test
143+
void registerReflectiveHintsForMethodWithHttpEntityParameter() throws NoSuchMethodException {
144+
Method method = SampleController.class.getDeclaredMethod("postHttpEntity", HttpEntity.class);
145+
processor.registerReflectionHints(hints, method);
146+
assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder(
147+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)),
148+
typeHint -> {
149+
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Request.class));
150+
assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder(
151+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
152+
MemberCategory.DECLARED_FIELDS);
153+
assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder(
154+
hint -> assertThat(hint.getName()).isEqualTo("getMessage"),
155+
hint -> assertThat(hint.getName()).isEqualTo("setMessage"));
156+
},
157+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class)));
158+
}
159+
160+
@Test
161+
void registerReflectiveHintsForMethodWithRawHttpEntityParameter() throws NoSuchMethodException {
162+
Method method = SampleController.class.getDeclaredMethod("postRawHttpEntity", HttpEntity.class);
163+
processor.registerReflectionHints(hints, method);
164+
assertThat(hints.typeHints()).singleElement().satisfies(
165+
typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)));
166+
}
167+
115168
static class SampleController {
116169

117170
@GetMapping
@@ -129,6 +182,25 @@ void post(@RequestBody Request request) {
129182
String message() {
130183
return "";
131184
}
185+
186+
@GetMapping
187+
HttpEntity<Response> getHttpEntity() {
188+
return new HttpEntity(new Response("response"));
189+
}
190+
191+
@GetMapping
192+
HttpEntity getRawHttpEntity() {
193+
return new HttpEntity(new Response("response"));
194+
}
195+
196+
@PostMapping
197+
void postHttpEntity(HttpEntity<Request> entity) {
198+
}
199+
200+
@PostMapping
201+
void postRawHttpEntity(HttpEntity entity) {
202+
}
203+
132204
}
133205

134206
@RestController

0 commit comments

Comments
 (0)