From 699da7c383fc594ef76fcc23a26983f8eadbfd7b Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 17 Jan 2024 17:44:38 +0100 Subject: [PATCH] =?UTF-8?q?Log=20warning=20if=20multiple=20@=E2=81=A0Reque?= =?UTF-8?q?stMapping=20annotations=20are=20declared?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If multiple request mapping annotations are discovered, Spring MVC and Spring WebFlux now log a warning similar to the following (without newlines). Multiple @RequestMapping annotations found on void org.example.MyController.put(), but only the first will be used: [ @org.springframework.web.bind.annotation.PutMapping(consumes={}, headers={}, name="", params={}, path={"/put"}, produces={}, value={"/put"}), @org.springframework.web.bind.annotation.PostMapping(consumes={}, headers={}, name="", params={}, path={"/put"}, produces={}, value={"/put"}) ] Closes gh-31962 --- .../controller/ann-requestmapping.adoc | 12 +++++ .../mvc-controller/ann-requestmapping.adoc | 12 +++++ .../web/bind/annotation/DeleteMapping.java | 9 +++- .../web/bind/annotation/GetMapping.java | 9 +++- .../web/bind/annotation/PatchMapping.java | 9 +++- .../web/bind/annotation/PostMapping.java | 9 +++- .../web/bind/annotation/PutMapping.java | 9 +++- .../web/bind/annotation/RequestMapping.java | 7 +++ .../RequestMappingHandlerMapping.java | 52 +++++++++++++++++-- .../RequestMappingHandlerMappingTests.java | 16 +++++- .../RequestMappingHandlerMapping.java | 51 ++++++++++++++++-- .../RequestMappingHandlerMappingTests.java | 13 ++++- 12 files changed, 191 insertions(+), 17 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc index 90e22574f8be..09b30a4a43cf 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux/controller/ann-requestmapping.adoc @@ -28,6 +28,12 @@ because, arguably, most controller methods should be mapped to a specific HTTP m using `@RequestMapping`, which, by default, matches to all HTTP methods. At the same time, a `@RequestMapping` is still needed at the class level to express shared mappings. +NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping` +annotations that are declared on the same element (class, interface, or method). If +multiple `@RequestMapping` annotations are detected on the same element, a warning will +be logged, and only the first mapping will be used. This also applies to composed +`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc. + The following example uses type and method level mappings: [tabs] @@ -439,6 +445,12 @@ controller methods should be mapped to a specific HTTP method versus using `@Req which, by default, matches to all HTTP methods. If you need an example of how to implement a composed annotation, look at how those are declared. +NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping` +annotations that are declared on the same element (class, interface, or method). If +multiple `@RequestMapping` annotations are detected on the same element, a warning will +be logged, and only the first mapping will be used. This also applies to composed +`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc. + Spring WebFlux also supports custom request mapping attributes with custom request matching logic. This is a more advanced option that requires sub-classing `RequestMappingHandlerMapping` and overriding the `getCustomMethodCondition` method, where diff --git a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc index 5e573f1e470f..fe929fda35e7 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc/mvc-controller/ann-requestmapping.adoc @@ -30,6 +30,12 @@ arguably, most controller methods should be mapped to a specific HTTP method ver using `@RequestMapping`, which, by default, matches to all HTTP methods. A `@RequestMapping` is still needed at the class level to express shared mappings. +NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping` +annotations that are declared on the same element (class, interface, or method). If +multiple `@RequestMapping` annotations are detected on the same element, a warning will +be logged, and only the first mapping will be used. This also applies to composed +`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc. + The following example has type and method level mappings: [tabs] @@ -489,6 +495,12 @@ controller methods should be mapped to a specific HTTP method versus using `@Req which, by default, matches to all HTTP methods. If you need an example of how to implement a composed annotation, look at how those are declared. +NOTE: `@RequestMapping` cannot be used in conjunction with other `@RequestMapping` +annotations that are declared on the same element (class, interface, or method). If +multiple `@RequestMapping` annotations are detected on the same element, a warning will +be logged, and only the first mapping will be used. This also applies to composed +`@RequestMapping` annotations such as `@GetMapping`, `@PostMapping`, etc. + Spring MVC also supports custom request-mapping attributes with custom request-matching logic. This is a more advanced option that requires subclassing `RequestMappingHandlerMapping` and overriding the `getCustomMethodCondition` method, where diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java index 1b9150ccf95b..4681a719a9ba 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/DeleteMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,13 @@ *
Specifically, {@code @DeleteMapping} is a composed annotation that * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.DELETE)}. * + *
NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same method. + * If multiple {@code @RequestMapping} annotations are detected on the same method, + * a warning will be logged, and only the first mapping will be used. This applies + * to {@code @RequestMapping} as well as composed {@code @RequestMapping} annotations + * such as {@code @GetMapping}, {@code @PostMapping}, etc. + * * @author Sam Brannen * @since 4.3 * @see GetMapping diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java index c9fc39bda34b..22092f77fc51 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/GetMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,13 @@ *
Specifically, {@code @GetMapping} is a composed annotation that * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.GET)}. * + *
NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same method. + * If multiple {@code @RequestMapping} annotations are detected on the same method, + * a warning will be logged, and only the first mapping will be used. This applies + * to {@code @RequestMapping} as well as composed {@code @RequestMapping} annotations + * such as {@code @PutMapping}, {@code @PostMapping}, etc. + * * @author Sam Brannen * @since 4.3 * @see PostMapping diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java index 72fd7111919f..c11f39e4b0a7 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PatchMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,13 @@ *
Specifically, {@code @PatchMapping} is a composed annotation that * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.PATCH)}. * + *
NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same method. + * If multiple {@code @RequestMapping} annotations are detected on the same method, + * a warning will be logged, and only the first mapping will be used. This applies + * to {@code @RequestMapping} as well as composed {@code @RequestMapping} annotations + * such as {@code @GetMapping}, {@code @PostMapping}, etc. + * * @author Sam Brannen * @since 4.3 * @see GetMapping diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java index f5a304038b33..18a0b47db553 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PostMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,13 @@ *
Specifically, {@code @PostMapping} is a composed annotation that * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.POST)}. * + *
NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same method. + * If multiple {@code @RequestMapping} annotations are detected on the same method, + * a warning will be logged, and only the first mapping will be used. This applies + * to {@code @RequestMapping} as well as composed {@code @RequestMapping} annotations + * such as {@code @GetMapping}, {@code @PutMapping}, etc. + * * @author Sam Brannen * @since 4.3 * @see GetMapping diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java index 0040291dbefb..8e8cb005d0a4 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/PutMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,13 @@ *
Specifically, {@code @PutMapping} is a composed annotation that * acts as a shortcut for {@code @RequestMapping(method = RequestMethod.PUT)}. * + *
NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same method. + * If multiple {@code @RequestMapping} annotations are detected on the same method, + * a warning will be logged, and only the first mapping will be used. This applies + * to {@code @RequestMapping} as well as composed {@code @RequestMapping} annotations + * such as {@code @GetMapping}, {@code @PostMapping}, etc. + * * @author Sam Brannen * @since 4.3 * @see GetMapping diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index 624281339e25..0bff16d474fb 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -54,6 +54,13 @@ * {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping}, or * {@link PatchMapping @PatchMapping}. * + *
NOTE: This annotation cannot be used in conjunction with + * other {@code @RequestMapping} annotations that are declared on the same element + * (class, interface, or method). If multiple {@code @RequestMapping} annotations + * are detected on the same element, a warning will be logged, and only the first + * mapping will be used. This also applies to composed {@code @RequestMapping} + * annotations such as {@code @GetMapping}, {@code @PostMapping}, etc. + * *
NOTE: When using controller interfaces (e.g. for AOP proxying),
* make sure to consistently put all your mapping annotations — such
* as {@code @RequestMapping} and {@code @SessionAttributes} — on
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 3bb494ac1738..e96cb1f9fd1c 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,18 +16,23 @@
package org.springframework.web.reactive.result.method.annotation;
+import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.MergedAnnotation;
+import org.springframework.core.annotation.MergedAnnotationPredicates;
import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
+import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
@@ -182,9 +187,20 @@ private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestCondition> customCondition = (element instanceof Class> clazz ?
getCustomTypeCondition(clazz) : getCustomMethodCondition((Method) element));
- RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
- if (requestMapping != null) {
- return createRequestMappingInfo(requestMapping, customCondition);
+ MergedAnnotations mergedAnnotations = MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY,
+ RepeatableContainers.none());
+ List