From 93b340e5633d623e0899dd296bda7ce86ce02b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 20 Jun 2022 18:08:44 +0200 Subject: [PATCH] Add reflection hints for HttpEntity For those used in Web controllers. Closes gh-28622 --- .../BindingReflectionHintsRegistrar.java | 5 +- .../RequestMappingReflectiveProcessor.java | 42 +++++++++-- ...equestMappingReflectiveProcessorTests.java | 72 +++++++++++++++++++ 3 files changed, 110 insertions(+), 9 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java index 974a0d34e1..714e0a0abc 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/BindingReflectionHintsRegistrar.java @@ -38,6 +38,7 @@ import org.springframework.aot.hint.TypeHint.Builder; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** @@ -123,8 +124,8 @@ public class BindingReflectionHintsRegistrar { } } - private void collectReferencedTypes(Set seen, Set> types, Type type) { - if (seen.contains(type)) { + private void collectReferencedTypes(Set seen, Set> types, @Nullable Type type) { + if (type == null || seen.contains(type)) { return; } seen.add(type); diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java index cdfdcc23bb..b76529d07e 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessor.java @@ -19,6 +19,7 @@ package org.springframework.web.bind.annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.lang.reflect.Type; import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.ReflectionHints; @@ -26,13 +27,18 @@ import org.springframework.aot.hint.annotation.ReflectiveProcessor; import org.springframework.aot.hint.support.BindingReflectionHintsRegistrar; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.http.HttpEntity; +import org.springframework.lang.Nullable; /** * {@link ReflectiveProcessor} implementation for {@link RequestMapping} * annotated types. On top of registering reflection hints for invoking - * the annotated method, this implementation handles return types annotated - * with {@link ResponseBody} and parameters annotated with {@link RequestBody} - * which are serialized as well. + * the annotated method, this implementation handles: + *
    + *
  • Return types annotated with {@link ResponseBody}.
  • + *
  • Parameters annotated with {@link RequestBody}.
  • + *
  • {@link HttpEntity} return type and parameters.
  • + *
* * @author Stephane Nicoll * @author Sebastien Deleuze @@ -45,29 +51,51 @@ class RequestMappingReflectiveProcessor implements ReflectiveProcessor { @Override public void registerReflectionHints(ReflectionHints hints, AnnotatedElement element) { if (element instanceof Class type) { - registerTypeHint(hints, type); + registerTypeHints(hints, type); } else if (element instanceof Method method) { - registerMethodHint(hints, method); + registerMethodHints(hints, method); } } - protected void registerTypeHint(ReflectionHints hints, Class type) { + protected void registerTypeHints(ReflectionHints hints, Class type) { hints.registerType(type, hint -> {}); } - protected void registerMethodHint(ReflectionHints hints, Method method) { + protected void registerMethodHints(ReflectionHints hints, Method method) { + hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE)); + registerParameterHints(hints, method); + registerReturnValueHints(hints, method); + } + + protected void registerParameterHints(ReflectionHints hints, Method method) { hints.registerMethod(method, hint -> hint.setModes(ExecutableMode.INVOKE)); for (Parameter parameter : method.getParameters()) { MethodParameter methodParameter = MethodParameter.forParameter(parameter); if (methodParameter.hasParameterAnnotation(RequestBody.class)) { this.bindingRegistrar.registerReflectionHints(hints, methodParameter.getGenericParameterType()); } + else if (HttpEntity.class.isAssignableFrom(methodParameter.getParameterType())) { + this.bindingRegistrar.registerReflectionHints(hints, getHttpEntityType(methodParameter)); + } } + } + + protected void registerReturnValueHints(ReflectionHints hints, Method method) { MethodParameter returnType = MethodParameter.forExecutable(method, -1); if (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)) { this.bindingRegistrar.registerReflectionHints(hints, returnType.getGenericParameterType()); } + else if (HttpEntity.class.isAssignableFrom(returnType.getParameterType())) { + this.bindingRegistrar.registerReflectionHints(hints, getHttpEntityType(returnType)); + } } + + @Nullable + protected Type getHttpEntityType(MethodParameter parameter) { + MethodParameter nestedParameter = parameter.nested(); + return (nestedParameter.getNestedParameterType() == nestedParameter.getParameterType() ? null : nestedParameter.getNestedParameterType()); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessorTests.java b/spring-web/src/test/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessorTests.java index 43c4a1f5bd..7a58aaa6f4 100644 --- a/spring-web/src/test/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/web/bind/annotation/RequestMappingReflectiveProcessorTests.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.TypeReference; +import org.springframework.http.HttpEntity; import static org.assertj.core.api.Assertions.assertThat; @@ -112,6 +113,58 @@ public class RequestMappingReflectiveProcessorTests { assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleControllerWithClassMapping.class))); } + @Test + void registerReflectiveHintsForMethodReturningHttpEntity() throws NoSuchMethodException { + Method method = SampleController.class.getDeclaredMethod("getHttpEntity"); + processor.registerReflectionHints(hints, method); + assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), + typeHint -> { + assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Response.class)); + assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS); + assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( + hint -> assertThat(hint.getName()).isEqualTo("getMessage"), + hint -> assertThat(hint.getName()).isEqualTo("setMessage")); + }, + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class))); + } + + @Test + void registerReflectiveHintsForMethodReturningRawHttpEntity() throws NoSuchMethodException { + Method method = SampleController.class.getDeclaredMethod("getRawHttpEntity"); + processor.registerReflectionHints(hints, method); + assertThat(hints.typeHints()).singleElement().satisfies( + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class))); + } + + @Test + void registerReflectiveHintsForMethodWithHttpEntityParameter() throws NoSuchMethodException { + Method method = SampleController.class.getDeclaredMethod("postHttpEntity", HttpEntity.class); + processor.registerReflectionHints(hints, method); + assertThat(hints.typeHints()).satisfiesExactlyInAnyOrder( + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class)), + typeHint -> { + assertThat(typeHint.getType()).isEqualTo(TypeReference.of(Request.class)); + assertThat(typeHint.getMemberCategories()).containsExactlyInAnyOrder( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.DECLARED_FIELDS); + assertThat(typeHint.methods()).satisfiesExactlyInAnyOrder( + hint -> assertThat(hint.getName()).isEqualTo("getMessage"), + hint -> assertThat(hint.getName()).isEqualTo("setMessage")); + }, + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(String.class))); + } + + @Test + void registerReflectiveHintsForMethodWithRawHttpEntityParameter() throws NoSuchMethodException { + Method method = SampleController.class.getDeclaredMethod("postRawHttpEntity", HttpEntity.class); + processor.registerReflectionHints(hints, method); + assertThat(hints.typeHints()).singleElement().satisfies( + typeHint -> assertThat(typeHint.getType()).isEqualTo(TypeReference.of(SampleController.class))); + } + static class SampleController { @GetMapping @@ -129,6 +182,25 @@ public class RequestMappingReflectiveProcessorTests { String message() { return ""; } + + @GetMapping + HttpEntity getHttpEntity() { + return new HttpEntity(new Response("response")); + } + + @GetMapping + HttpEntity getRawHttpEntity() { + return new HttpEntity(new Response("response")); + } + + @PostMapping + void postHttpEntity(HttpEntity entity) { + } + + @PostMapping + void postRawHttpEntity(HttpEntity entity) { + } + } @RestController