Log warning if multiple @RequestMapping annotations are declared
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
This commit is contained in:
@@ -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,6 +16,7 @@
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
@@ -30,7 +31,10 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
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;
|
||||
@@ -343,9 +347,20 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
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<AnnotationDescriptor<RequestMapping>> requestMappings = mergedAnnotations.stream(RequestMapping.class)
|
||||
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
|
||||
.map(AnnotationDescriptor::new)
|
||||
.distinct()
|
||||
.toList();
|
||||
|
||||
if (!requestMappings.isEmpty()) {
|
||||
if (requestMappings.size() > 1 && logger.isWarnEnabled()) {
|
||||
logger.warn("Multiple @RequestMapping annotations found on %s, but only the first will be used: %s"
|
||||
.formatted(element, requestMappings));
|
||||
}
|
||||
return createRequestMappingInfo(requestMappings.get(0).annotation, customCondition);
|
||||
}
|
||||
|
||||
HttpExchange httpExchange = AnnotatedElementUtils.findMergedAnnotation(element, HttpExchange.class);
|
||||
@@ -594,4 +609,32 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
|
||||
}
|
||||
}
|
||||
|
||||
private static class AnnotationDescriptor<A extends Annotation> {
|
||||
|
||||
private final A annotation;
|
||||
private final Annotation source;
|
||||
|
||||
AnnotationDescriptor(MergedAnnotation<A> mergedAnnotation) {
|
||||
this.annotation = mergedAnnotation.synthesize();
|
||||
this.source = (mergedAnnotation.getDistance() > 0 ?
|
||||
mergedAnnotation.getRoot().synthesize() : this.annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return (obj instanceof AnnotationDescriptor<?> that && this.annotation.equals(that.annotation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.annotation.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.source.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@@ -363,7 +362,9 @@ class RequestMappingHandlerMappingTests {
|
||||
|
||||
|
||||
@Controller
|
||||
// gh-31962: The presence of multiple @RequestMappings is intentional.
|
||||
@RequestMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ExtraRequestMapping
|
||||
static class ComposedAnnotationController {
|
||||
|
||||
@RequestMapping
|
||||
@@ -382,7 +383,10 @@ class RequestMappingHandlerMappingTests {
|
||||
public void post(@RequestBody(required = false) Foo foo) {
|
||||
}
|
||||
|
||||
@PutMapping("/put")
|
||||
// gh-31962: The presence of multiple @RequestMappings is intentional.
|
||||
@PatchMapping("/put")
|
||||
@RequestMapping(path = "/put", method = RequestMethod.PUT) // local @RequestMapping overrides meta-annotations
|
||||
@PostMapping("/put")
|
||||
public void put() {
|
||||
}
|
||||
|
||||
@@ -396,6 +400,11 @@ class RequestMappingHandlerMappingTests {
|
||||
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ExtraRequestMapping {
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE,
|
||||
|
||||
Reference in New Issue
Block a user