Merge 41b755e467
into 3ce7613195
This commit is contained in:
commit
b343407085
|
@ -19,7 +19,6 @@ package org.springframework.web.reactive.result.method.annotation;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Parameter;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -30,7 +29,9 @@ import java.util.stream.Stream;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.context.EmbeddedValueResolverAware;
|
import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotatedMethod;
|
||||||
import org.springframework.core.annotation.MergedAnnotation;
|
import org.springframework.core.annotation.MergedAnnotation;
|
||||||
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
||||||
import org.springframework.core.annotation.MergedAnnotations;
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
|
@ -375,13 +376,17 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
|
|
||||||
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
|
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
|
||||||
ConsumesRequestCondition condition = info.getConsumesCondition();
|
ConsumesRequestCondition condition = info.getConsumesCondition();
|
||||||
if (!condition.isEmpty()) {
|
if (condition.isEmpty()) {
|
||||||
for (Parameter parameter : method.getParameters()) {
|
return;
|
||||||
MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
|
}
|
||||||
if (annot.isPresent()) {
|
|
||||||
condition.setBodyRequired(annot.getBoolean("required"));
|
AnnotatedMethod annotatedMethod = new AnnotatedMethod(method);
|
||||||
break;
|
|
||||||
}
|
for (MethodParameter parameter : annotatedMethod.getMethodParameters()) {
|
||||||
|
RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
|
||||||
|
if (requestBody != null) {
|
||||||
|
condition.setBodyRequired(requestBody.required());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -323,6 +323,48 @@ class RequestMappingHandlerMappingTests {
|
||||||
.containsExactly("h1=hv1", "!h2");
|
.containsExactly("h1=hv1", "!h2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requestBodyAnnotationFromInterfaceIsRespected() {
|
||||||
|
this.handlerMapping.afterPropertiesSet();
|
||||||
|
|
||||||
|
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
||||||
|
mapping.setApplicationContext(new StaticWebApplicationContext());
|
||||||
|
mapping.afterPropertiesSet();
|
||||||
|
|
||||||
|
Class<InterfaceControllerImpl> clazz = InterfaceControllerImpl.class;
|
||||||
|
Method method = ReflectionUtils.findMethod(clazz, "post", Foo.class);
|
||||||
|
assertThat(method).isNotNull();
|
||||||
|
|
||||||
|
RequestMappingInfo info = mapping.getMappingForMethod(method, clazz);
|
||||||
|
assertThat(info).isNotNull();
|
||||||
|
|
||||||
|
mapping.registerHandlerMethod(new InterfaceControllerImpl(), method, info);
|
||||||
|
assertThat(info.getConsumesCondition()).isNotNull();
|
||||||
|
assertThat(info.getConsumesCondition().isBodyRequired()).isFalse();
|
||||||
|
assertThat(info.getConsumesCondition().getConsumableMediaTypes()).containsOnly(MediaType.APPLICATION_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requestBodyAnnotationFromImplementationOverridesInterface() {
|
||||||
|
this.handlerMapping.afterPropertiesSet();
|
||||||
|
|
||||||
|
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
||||||
|
mapping.setApplicationContext(new StaticWebApplicationContext());
|
||||||
|
mapping.afterPropertiesSet();
|
||||||
|
|
||||||
|
Class<InterfaceControllerImplOverridesRequestBody> clazz = InterfaceControllerImplOverridesRequestBody.class;
|
||||||
|
Method method = ReflectionUtils.findMethod(clazz, "post", Foo.class);
|
||||||
|
assertThat(method).isNotNull();
|
||||||
|
|
||||||
|
RequestMappingInfo info = mapping.getMappingForMethod(method, clazz);
|
||||||
|
assertThat(info).isNotNull();
|
||||||
|
|
||||||
|
mapping.registerHandlerMethod(new InterfaceControllerImplOverridesRequestBody(), method, info);
|
||||||
|
assertThat(info.getConsumesCondition()).isNotNull();
|
||||||
|
assertThat(info.getConsumesCondition().isBodyRequired()).isTrue();
|
||||||
|
assertThat(info.getConsumesCondition().getConsumableMediaTypes()).containsOnly(MediaType.APPLICATION_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) {
|
private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) {
|
||||||
String methodName = requestMethod.name().toLowerCase();
|
String methodName = requestMethod.name().toLowerCase();
|
||||||
String path = "/" + methodName;
|
String path = "/" + methodName;
|
||||||
|
@ -501,6 +543,22 @@ class RequestMappingHandlerMappingTests {
|
||||||
public void post() {}
|
public void post() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping(value = "/controller", consumes = { "application/json" })
|
||||||
|
interface InterfaceController {
|
||||||
|
@PostMapping("/postMapping")
|
||||||
|
void post(@RequestBody(required = false) Foo foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class InterfaceControllerImpl implements InterfaceController {
|
||||||
|
@Override
|
||||||
|
public void post(Foo foo) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class InterfaceControllerImplOverridesRequestBody implements InterfaceController {
|
||||||
|
@Override
|
||||||
|
public void post(@RequestBody(required = true) Foo foo) {}
|
||||||
|
}
|
||||||
|
|
||||||
@HttpExchange
|
@HttpExchange
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Parameter;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -31,7 +30,9 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.context.EmbeddedValueResolverAware;
|
import org.springframework.context.EmbeddedValueResolverAware;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotatedMethod;
|
||||||
import org.springframework.core.annotation.MergedAnnotation;
|
import org.springframework.core.annotation.MergedAnnotation;
|
||||||
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
||||||
import org.springframework.core.annotation.MergedAnnotations;
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
|
@ -415,13 +416,17 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||||
|
|
||||||
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
|
private void updateConsumesCondition(RequestMappingInfo info, Method method) {
|
||||||
ConsumesRequestCondition condition = info.getConsumesCondition();
|
ConsumesRequestCondition condition = info.getConsumesCondition();
|
||||||
if (!condition.isEmpty()) {
|
if (condition.isEmpty()) {
|
||||||
for (Parameter parameter : method.getParameters()) {
|
return;
|
||||||
MergedAnnotation<RequestBody> annot = MergedAnnotations.from(parameter).get(RequestBody.class);
|
}
|
||||||
if (annot.isPresent()) {
|
|
||||||
condition.setBodyRequired(annot.getBoolean("required"));
|
AnnotatedMethod annotatedMethod = new AnnotatedMethod(method);
|
||||||
break;
|
|
||||||
}
|
for (MethodParameter parameter : annotatedMethod.getMethodParameters()) {
|
||||||
|
RequestBody requestBody = parameter.getParameterAnnotation(RequestBody.class);
|
||||||
|
if (requestBody != null) {
|
||||||
|
condition.setBodyRequired(requestBody.required());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -386,6 +386,52 @@ class RequestMappingHandlerMappingTests {
|
||||||
.containsExactly("h1=hv1", "!h2");
|
.containsExactly("h1=hv1", "!h2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requestBodyAnnotationFromInterfaceIsRespected() throws Exception {
|
||||||
|
RequestMappingHandlerMapping mapping = createMapping();
|
||||||
|
|
||||||
|
Class<?> controllerClass = InterfaceControllerImpl.class;
|
||||||
|
Method method = controllerClass.getDeclaredMethod("post", Foo.class);
|
||||||
|
|
||||||
|
RequestMappingInfo info = mapping.getMappingForMethod(method, controllerClass);
|
||||||
|
assertThat(info).isNotNull();
|
||||||
|
|
||||||
|
mapping.registerHandlerMethod(new InterfaceControllerImpl(), method, info);
|
||||||
|
|
||||||
|
assertThat(info.getConsumesCondition()).isNotNull();
|
||||||
|
assertThat(info.getConsumesCondition().isBodyRequired()).isFalse();
|
||||||
|
assertThat(info.getConsumesCondition().getConsumableMediaTypes()).containsOnly(MediaType.APPLICATION_JSON);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/controller/postMapping");
|
||||||
|
initRequestPath(mapping, request);
|
||||||
|
|
||||||
|
RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
|
||||||
|
assertThat(matchingInfo).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void requestBodyAnnotationFromImplementationOverridesInterface() throws Exception {
|
||||||
|
RequestMappingHandlerMapping mapping = createMapping();
|
||||||
|
|
||||||
|
Class<?> controllerClass = InterfaceControllerImplOverridesRequestBody.class;
|
||||||
|
Method method = controllerClass.getDeclaredMethod("post", Foo.class);
|
||||||
|
|
||||||
|
RequestMappingInfo info = mapping.getMappingForMethod(method, controllerClass);
|
||||||
|
assertThat(info).isNotNull();
|
||||||
|
|
||||||
|
mapping.registerHandlerMethod(new InterfaceControllerImplOverridesRequestBody(), method, info);
|
||||||
|
|
||||||
|
assertThat(info.getConsumesCondition()).isNotNull();
|
||||||
|
assertThat(info.getConsumesCondition().isBodyRequired()).isTrue();
|
||||||
|
assertThat(info.getConsumesCondition().getConsumableMediaTypes()).containsOnly(MediaType.APPLICATION_JSON);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/controller/postMapping");
|
||||||
|
initRequestPath(mapping, request);
|
||||||
|
|
||||||
|
RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
|
||||||
|
assertThat(matchingInfo).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
private static RequestMappingHandlerMapping createMapping() {
|
private static RequestMappingHandlerMapping createMapping() {
|
||||||
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
|
||||||
mapping.setApplicationContext(new StaticWebApplicationContext());
|
mapping.setApplicationContext(new StaticWebApplicationContext());
|
||||||
|
@ -571,6 +617,22 @@ class RequestMappingHandlerMappingTests {
|
||||||
public void post() {}
|
public void post() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = "/controller", consumes = { "application/json" })
|
||||||
|
interface InterfaceController {
|
||||||
|
@PostMapping("/postMapping")
|
||||||
|
void post(@RequestBody(required = false) Foo foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class InterfaceControllerImpl implements InterfaceController {
|
||||||
|
@Override
|
||||||
|
public void post(Foo foo) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class InterfaceControllerImplOverridesRequestBody implements InterfaceController {
|
||||||
|
@Override
|
||||||
|
public void post(@RequestBody(required = true) Foo foo) {}
|
||||||
|
}
|
||||||
|
|
||||||
@HttpExchange
|
@HttpExchange
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
|
|
Loading…
Reference in New Issue