From 4d34444a699d0e083d37d922781b9ce4731c004a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Fri, 19 Apr 2024 14:24:16 +0200 Subject: [PATCH] Consistent annotation lookup in MvcUriComponentsBuilder Previously, a UriComponents build based on a method would require the given method to be the annotated one as method parameter resolution only applied locally. This was a problem when a controller was specified on a method whose mapping is defined in a parent. This commit harmonies the lookup using AnnotatedMethod who provides support for a synthesized method parameter that takes annotations from parent parameter into account. Closes gh-32553 --- .../annotation/MvcUriComponentsBuilder.java | 24 ++++++++++-------- .../MvcUriComponentsBuilderTests.java | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index ea2e301068..c66bb0652e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -42,7 +42,7 @@ import org.springframework.core.MethodIntrospector; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.core.annotation.SynthesizingMethodParameter; +import org.springframework.core.annotation.AnnotatedMethod; import org.springframework.lang.Nullable; import org.springframework.objenesis.ObjenesisException; import org.springframework.objenesis.SpringObjenesis; @@ -538,6 +538,7 @@ public class MvcUriComponentsBuilder { private static UriComponentsBuilder fromMethodInternal(@Nullable UriComponentsBuilder builder, Class controllerType, Method method, Object... args) { + AnnotatedMethod annotatedMethod = new AnnotatedMethod(method); builder = getBaseUrlToUse(builder); // Externally configured prefix via PathConfigurer.. @@ -545,7 +546,7 @@ public class MvcUriComponentsBuilder { builder.path(prefix); String typePath = getClassMapping(controllerType); - String methodPath = getMethodMapping(method); + String methodPath = getMethodMapping(annotatedMethod); String path = pathMatcher.combine(typePath, methodPath); path = PathPatternParser.defaultInstance.initFullPathPattern(path); if (!StringUtils.hasText(prefix + path)) { @@ -553,7 +554,7 @@ public class MvcUriComponentsBuilder { } builder.path(path); - return applyContributors(builder, method, args); + return applyContributors(builder, annotatedMethod, args); } private static UriComponentsBuilder getBaseUrlToUse(@Nullable UriComponentsBuilder baseUrl) { @@ -587,13 +588,12 @@ public class MvcUriComponentsBuilder { return getPathMapping(mapping, controllerType.getName()); } - private static String getMethodMapping(Method method) { - Assert.notNull(method, "'method' must not be null"); - RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class); + private static String getMethodMapping(AnnotatedMethod annotatedMethod) { + RequestMapping requestMapping = annotatedMethod.getMethodAnnotation(RequestMapping.class); if (requestMapping == null) { - throw new IllegalArgumentException("No @RequestMapping on: " + method.toGenericString()); + throw new IllegalArgumentException("No @RequestMapping on: " + annotatedMethod.getMethod().toGenericString()); } - return getPathMapping(requestMapping, method.toGenericString()); + return getPathMapping(requestMapping, annotatedMethod.getMethod().toGenericString()); } private static String getPathMapping(RequestMapping requestMapping, String source) { @@ -628,10 +628,12 @@ public class MvcUriComponentsBuilder { } } - private static UriComponentsBuilder applyContributors(UriComponentsBuilder builder, Method method, Object... args) { + private static UriComponentsBuilder applyContributors(UriComponentsBuilder builder, + AnnotatedMethod annotatedMethod, Object... args) { + CompositeUriComponentsContributor contributor = getUriComponentsContributor(); - int paramCount = method.getParameterCount(); + int paramCount = annotatedMethod.getMethodParameters().length; int argCount = args.length; if (paramCount != argCount) { throw new IllegalArgumentException("Number of method parameters " + paramCount + @@ -640,7 +642,7 @@ public class MvcUriComponentsBuilder { final Map uriVars = new HashMap<>(); for (int i = 0; i < paramCount; i++) { - MethodParameter param = new SynthesizingMethodParameter(method, i); + MethodParameter param = annotatedMethod.getMethodParameters()[i]; param.initParameterNameDiscovery(parameterNameDiscoverer); contributor.contributeMethodArgument(param, args[i], builder, uriVars); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java index 14a52fc93e..2e96c2f788 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java @@ -42,6 +42,7 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.http.HttpEntity; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.lang.Nullable; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; @@ -334,6 +335,14 @@ public class MvcUriComponentsBuilderTests { assertThat(uriComponents.toUriString()).isEqualTo("http://localhost/something/custom/1/foo"); } + @Test + void fromMethodNameWithAnnotationsOnInterface() { + initWebApplicationContext(WebConfig.class); + UriComponents uriComponents = fromMethodName(HelloController.class, "get", "test").build(); + + assertThat(uriComponents.toString()).isEqualTo("http://localhost/hello/test"); + } + @Test void fromMethodCallOnSubclass() { UriComponents uriComponents = fromMethodCall(on(ExtendedController.class).myMethod(null)).build(); @@ -855,4 +864,20 @@ public class MvcUriComponentsBuilderTests { } } + interface HelloInterface { + + @GetMapping("/hello/{name}") + ResponseEntity get(@PathVariable String name); + } + + @Controller + static class HelloController implements HelloInterface { + + @Override + public ResponseEntity get(String name) { + return ResponseEntity.ok("Hello " + name); + } + + } + }