diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java index 631141e4cb..e4562a16c2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java @@ -172,32 +172,26 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi return info; } - /** - * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)} - * or {@link #createRequestMappingInfo(HttpExchange, RequestCondition)}, - * depending on which annotation is found on the element being processed, - * and supplying the appropriate custom {@link RequestCondition} depending on whether - * the supplied {@code annotatedElement} is a class or method. - * @see #getCustomTypeCondition(Class) - * @see #getCustomMethodCondition(Method) - */ @Nullable private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { - RequestCondition condition = (element instanceof Class clazz ? + RequestCondition customCondition = (element instanceof Class clazz ? getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element)); + RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); - if(requestMapping != null){ - return createRequestMappingInfo(requestMapping, condition); + if (requestMapping != null){ + return createRequestMappingInfo(requestMapping, customCondition); } + HttpExchange httpExchange = AnnotatedElementUtils.findMergedAnnotation(element, HttpExchange.class); - if(httpExchange != null){ - return createRequestMappingInfo(httpExchange, condition); + if (httpExchange != null){ + return createRequestMappingInfo(httpExchange, customCondition); } + return null; } /** - * Provide a custom type-level request condition. + * Protected method to provide a custom type-level request condition. * The custom {@link RequestCondition} can be of any type so long as the * same condition type is returned from all calls to this method in order * to ensure custom request conditions can be combined and compared. @@ -216,7 +210,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } /** - * Provide a custom method-level request condition. + * Protected method to provide a custom method-level request condition. * The custom {@link RequestCondition} can be of any type so long as the * same condition type is returned from all calls to this method in order * to ensure custom request conditions can be combined and compared. @@ -236,9 +230,9 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi /** * Create a {@link RequestMappingInfo} from the supplied - * {@link RequestMapping @RequestMapping} annotation, which is either - * a directly declared annotation, a meta-annotation, or the synthesized - * result of merging annotation attributes within an annotation hierarchy. + * {@link RequestMapping @RequestMapping} annotation, or meta-annotation, + * or synthesized result of merging annotation attributes within an + * annotation hierarchy. */ protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition customCondition) { @@ -251,31 +245,34 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name()); + if (customCondition != null) { builder.customCondition(customCondition); } + return builder.options(this.config).build(); } /** * Create a {@link RequestMappingInfo} from the supplied - * {@link HttpExchange @HttpExchange} annotation, which is either - * a directly declared annotation, a meta-annotation, or the synthesized - * result of merging annotation attributes within an annotation hierarchy. + * {@link HttpExchange @HttpExchange} annotation, or meta-annotation, + * or synthesized result of merging annotation attributes within an + * annotation hierarchy. + * @since 6.1 */ protected RequestMappingInfo createRequestMappingInfo( - HttpExchange httpExchange, - @Nullable RequestCondition customCondition) { + HttpExchange httpExchange, @Nullable RequestCondition customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo - .paths(resolveEmbeddedValuesInPatterns( - toTextArray(httpExchange.value()))) + .paths(resolveEmbeddedValuesInPatterns(toStringArray(httpExchange.value()))) .methods(toMethodArray(httpExchange.method())) - .consumes(toTextArray(httpExchange.contentType())) + .consumes(toStringArray(httpExchange.contentType())) .produces(httpExchange.accept()); + if (customCondition != null) { builder.customCondition(customCondition); } + return builder.options(this.config).build(); } @@ -296,24 +293,15 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } } - - private static String[] toTextArray(String string) { - if (StringUtils.hasText(string)) { - return new String[] { string }; - } - return new String[] {}; + private static String[] toStringArray(String value) { + return StringUtils.hasText(value) ? new String[] {value} : new String[] {}; } private static RequestMethod[] toMethodArray(String method) { - RequestMethod requestMethod = null; - if (StringUtils.hasText(method)) { - requestMethod = RequestMethod.resolve(method); - } - return requestMethod != null ? new RequestMethod[] { requestMethod } - : new RequestMethod[] {}; + return (StringUtils.hasText(method) ? + new RequestMethod[] {RequestMethod.valueOf(method)} : new RequestMethod[] {}); } - @Override public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) { super.registerMapping(mapping, handler, method); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java index 7b2f285b18..2dfe4bde2d 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMappingTests.java @@ -48,6 +48,7 @@ import org.springframework.web.reactive.result.condition.MediaTypeExpression; import org.springframework.web.reactive.result.condition.PatternsRequestCondition; import org.springframework.web.reactive.result.method.RequestMappingInfo; import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPatternParser; @@ -97,7 +98,7 @@ class RequestMappingHandlerMappingTests { } @Test - void resolveRequestMappingViaComposedAnnotation() throws Exception { + void resolveRequestMappingViaComposedAnnotation() { RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST); assertThat(info.getConsumesCondition().getConsumableMediaTypes().iterator().next().toString()).isEqualTo(MediaType.APPLICATION_JSON_VALUE); @@ -105,7 +106,7 @@ class RequestMappingHandlerMappingTests { } @Test // SPR-14988 - void getMappingOverridesConsumesFromTypeLevelAnnotation() throws Exception { + void getMappingOverridesConsumesFromTypeLevelAnnotation() { RequestMappingInfo requestMappingInfo = assertComposedAnnotationMapping(RequestMethod.POST); ConsumesRequestCondition condition = requestMappingInfo.getConsumesCondition(); @@ -129,27 +130,27 @@ class RequestMappingHandlerMappingTests { } @Test - void getMapping() throws Exception { + void getMapping() { assertComposedAnnotationMapping(RequestMethod.GET); } @Test - void postMapping() throws Exception { + void postMapping() { assertComposedAnnotationMapping(RequestMethod.POST); } @Test - void putMapping() throws Exception { + void putMapping() { assertComposedAnnotationMapping(RequestMethod.PUT); } @Test - void deleteMapping() throws Exception { + void deleteMapping() { assertComposedAnnotationMapping(RequestMethod.DELETE); } @Test - void patchMapping() throws Exception { + void patchMapping() { assertComposedAnnotationMapping(RequestMethod.PATCH); } @@ -165,6 +166,7 @@ class RequestMappingHandlerMappingTests { assertThat(mappingInfo.getPatternsCondition().getPatterns()) .extracting(PathPattern::toString) .containsOnly("/exchange"); + assertThat(mappingInfo.getMethodsCondition().getMethods()).isEmpty(); assertThat(mappingInfo.getParamsCondition().getExpressions()).isEmpty(); assertThat(mappingInfo.getHeadersCondition().getExpressions()).isEmpty(); @@ -188,27 +190,28 @@ class RequestMappingHandlerMappingTests { assertThat(mappingInfo.getPatternsCondition().getPatterns()) .extracting(PathPattern::toString) .containsOnly("/exchange/custom"); - assertThat(mappingInfo.getMethodsCondition().getMethods()) - .containsOnly(RequestMethod.POST); + + assertThat(mappingInfo.getMethodsCondition().getMethods()).containsOnly(RequestMethod.POST); assertThat(mappingInfo.getParamsCondition().getExpressions()).isEmpty(); assertThat(mappingInfo.getHeadersCondition().getExpressions()).isEmpty(); + assertThat(mappingInfo.getConsumesCondition().getExpressions()) .extracting(MediaTypeExpression::getMediaType) .containsOnly(MediaType.APPLICATION_JSON); + assertThat(mappingInfo.getProducesCondition().getExpressions()) .extracting(MediaTypeExpression::getMediaType) .containsOnly(MediaType.valueOf("text/plain;charset=UTF-8")); } - private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception { + private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) { String methodName = requestMethod.name().toLowerCase(); String path = "/" + methodName; - return assertComposedAnnotationMapping(methodName, path, requestMethod); } - private RequestMappingInfo assertComposedAnnotationMapping(String methodName, String path, - RequestMethod requestMethod) throws Exception { + private RequestMappingInfo assertComposedAnnotationMapping( + String methodName, String path, RequestMethod requestMethod) { Class clazz = ComposedAnnotationController.class; Method method = ClassUtils.getMethod(clazz, methodName, (Class[]) null); @@ -287,15 +290,15 @@ class RequestMappingHandlerMappingTests { } } + @RestController @HttpExchange("/exchange") static class HttpExchangeController { @HttpExchange - public void defaultValuesExchange(){} + public void defaultValuesExchange() {} - @HttpExchange(value = "/custom", accept = "text/plain;charset=UTF-8", - method = "POST", contentType = "application/json") + @PostExchange(url = "/custom", contentType = "application/json", accept = "text/plain;charset=UTF-8") public void customValuesExchange(){} } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 73a29d0059..3924be9bcd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -333,27 +333,21 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi return null; } - /** - * Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)} - * or {@link #createRequestMappingInfo(HttpExchange, RequestCondition)}, - * depending on which annotation is found on the element being processed, - * and supplying the appropriate custom {@link RequestCondition} depending on whether - * the supplied {@code annotatedElement} is a class or method. - * @see #getCustomTypeCondition(Class) - * @see #getCustomMethodCondition(Method) - */ @Nullable private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { - RequestCondition condition = (element instanceof Class clazz ? + RequestCondition customCondition = (element instanceof Class clazz ? getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element)); + RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); if(requestMapping != null){ - return createRequestMappingInfo(requestMapping, condition); + return createRequestMappingInfo(requestMapping, customCondition); } + HttpExchange httpExchange = AnnotatedElementUtils.findMergedAnnotation(element, HttpExchange.class); if(httpExchange != null){ - return createRequestMappingInfo(httpExchange, condition); + return createRequestMappingInfo(httpExchange, customCondition); } + return null; } @@ -391,9 +385,9 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi /** * Create a {@link RequestMappingInfo} from the supplied - * {@link RequestMapping @RequestMapping} annotation, which is either - * a directly declared annotation, a meta-annotation, or the synthesized - * result of merging annotation attributes within an annotation hierarchy. + * {@link RequestMapping @RequestMapping} annotation, or meta-annotation, + * or synthesized result of merging annotation attributes within an + * annotation hierarchy. */ protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, @Nullable RequestCondition customCondition) { @@ -406,31 +400,34 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name()); + if (customCondition != null) { builder.customCondition(customCondition); } + return builder.options(this.config).build(); } /** * Create a {@link RequestMappingInfo} from the supplied - * {@link HttpExchange @HttpExchange} annotation, which is either - * a directly declared annotation, a meta-annotation, or the synthesized - * result of merging annotation attributes within an annotation hierarchy. + * {@link HttpExchange @HttpExchange} annotation, or meta-annotation, + * or synthesized result of merging annotation attributes within an + * annotation hierarchy. + * @since 6.1 */ protected RequestMappingInfo createRequestMappingInfo( - HttpExchange httpExchange, - @Nullable RequestCondition customCondition) { + HttpExchange httpExchange, @Nullable RequestCondition customCondition) { RequestMappingInfo.Builder builder = RequestMappingInfo - .paths(resolveEmbeddedValuesInPatterns( - toTextArray(httpExchange.value()))) + .paths(resolveEmbeddedValuesInPatterns(toStringArray(httpExchange.value()))) .methods(toMethodArray(httpExchange.method())) - .consumes(toTextArray(httpExchange.contentType())) + .consumes(toStringArray(httpExchange.contentType())) .produces(httpExchange.accept()); + if (customCondition != null) { builder.customCondition(customCondition); } + return builder.options(this.config).build(); } @@ -452,23 +449,15 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } - private static String[] toTextArray(String string) { - if (StringUtils.hasText(string)) { - return new String[] { string }; - } - return new String[] {}; + private static String[] toStringArray(String value) { + return (StringUtils.hasText(value) ? new String[] {value} : new String[] {}); } private static RequestMethod[] toMethodArray(String method) { - RequestMethod requestMethod = null; - if (StringUtils.hasText(method)) { - requestMethod = RequestMethod.resolve(method); - } - return requestMethod != null ? new RequestMethod[] { requestMethod } - : new RequestMethod[] {}; + return (StringUtils.hasText(method) ? + new RequestMethod[] {RequestMethod.valueOf(method)} : new RequestMethod[] {}); } - @Override public void registerMapping(RequestMappingInfo mapping, Object handler, Method method) { super.registerMapping(mapping, handler, method); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java index 2cd69987be..7af669534a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java @@ -48,6 +48,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.method.HandlerTypePredicate; import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; import org.springframework.web.servlet.handler.PathPatternsParameterizedTest; import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition; import org.springframework.web.servlet.mvc.condition.MediaTypeExpression; @@ -237,7 +238,7 @@ public class RequestMappingHandlerMappingTests { } @Test // SPR-14988 - void getMappingOverridesConsumesFromTypeLevelAnnotation() throws Exception { + void getMappingOverridesConsumesFromTypeLevelAnnotation() { RequestMappingInfo requestMappingInfo = assertComposedAnnotationMapping(RequestMethod.POST); ConsumesRequestCondition condition = requestMappingInfo.getConsumesCondition(); @@ -258,27 +259,27 @@ public class RequestMappingHandlerMappingTests { } @Test - void getMapping() throws Exception { + void getMapping() { assertComposedAnnotationMapping(RequestMethod.GET); } @Test - void postMapping() throws Exception { + void postMapping() { assertComposedAnnotationMapping(RequestMethod.POST); } @Test - void putMapping() throws Exception { + void putMapping() { assertComposedAnnotationMapping(RequestMethod.PUT); } @Test - void deleteMapping() throws Exception { + void deleteMapping() { assertComposedAnnotationMapping(RequestMethod.DELETE); } @Test - void patchMapping() throws Exception { + void patchMapping() { assertComposedAnnotationMapping(RequestMethod.PATCH); } @@ -296,6 +297,7 @@ public class RequestMappingHandlerMappingTests { assertThat(mappingInfo.getPathPatternsCondition().getPatterns()) .extracting(PathPattern::toString) .containsOnly("/exchange"); + assertThat(mappingInfo.getMethodsCondition().getMethods()).isEmpty(); assertThat(mappingInfo.getParamsCondition().getExpressions()).isEmpty(); assertThat(mappingInfo.getHeadersCondition().getExpressions()).isEmpty(); @@ -305,7 +307,7 @@ public class RequestMappingHandlerMappingTests { @SuppressWarnings("DataFlowIssue") @Test - void httpExchangeWithCustomValues() throws NoSuchMethodException { + void httpExchangeWithCustomValues() throws Exception { RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); mapping.setApplicationContext(new StaticWebApplicationContext()); mapping.afterPropertiesSet(); @@ -317,20 +319,21 @@ public class RequestMappingHandlerMappingTests { assertThat(mappingInfo.getPathPatternsCondition().getPatterns()) .extracting(PathPattern::toString) .containsOnly("/exchange/custom"); - assertThat(mappingInfo.getMethodsCondition().getMethods()) - .containsOnly(RequestMethod.POST); + + assertThat(mappingInfo.getMethodsCondition().getMethods()).containsOnly(RequestMethod.POST); assertThat(mappingInfo.getParamsCondition().getExpressions()).isEmpty(); assertThat(mappingInfo.getHeadersCondition().getExpressions()).isEmpty(); + assertThat(mappingInfo.getConsumesCondition().getExpressions()) .extracting(MediaTypeExpression::getMediaType) .containsOnly(MediaType.APPLICATION_JSON); + assertThat(mappingInfo.getProducesCondition().getExpressions()) .extracting(MediaTypeExpression::getMediaType) .containsOnly(MediaType.valueOf("text/plain;charset=UTF-8")); } - private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) throws Exception { - + private RequestMappingInfo assertComposedAnnotationMapping(RequestMethod requestMethod) { RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping(); mapping.setApplicationContext(new StaticWebApplicationContext()); @@ -418,15 +421,15 @@ public class RequestMappingHandlerMappingTests { } } + @RestController @HttpExchange("/exchange") static class HttpExchangeController { @HttpExchange - public void defaultValuesExchange(){} + public void defaultValuesExchange() {} - @HttpExchange(value = "/custom", accept = "text/plain;charset=UTF-8", - method = "POST", contentType = "application/json") + @PostExchange(url = "/custom", contentType = "application/json", accept = "text/plain;charset=UTF-8") public void customValuesExchange(){} }