Skip to content

Commit

Permalink
Consistent annotation lookup in MvcUriComponentsBuilder
Browse files Browse the repository at this point in the history
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
  • Loading branch information
snicoll committed Apr 23, 2024
1 parent d4ddbd5 commit 4d34444
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
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;
Expand Down Expand Up @@ -538,22 +538,23 @@ public UriComponentsBuilder withMethod(Class<?> controllerType, Method method, O
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..
String prefix = getPathPrefix(controllerType);
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)) {
path = "/";
}
builder.path(path);

return applyContributors(builder, method, args);
return applyContributors(builder, annotatedMethod, args);
}

private static UriComponentsBuilder getBaseUrlToUse(@Nullable UriComponentsBuilder baseUrl) {
Expand Down Expand Up @@ -587,13 +588,12 @@ private static String getClassMapping(Class<?> controllerType) {
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) {
Expand Down Expand Up @@ -628,10 +628,12 @@ else if (methods.size() > 1) {
}
}

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 +
Expand All @@ -640,7 +642,7 @@ private static UriComponentsBuilder applyContributors(UriComponentsBuilder build

final Map<String, Object> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
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;
Expand Down Expand Up @@ -334,6 +335,14 @@ void fromMethodNameConfigurablePath() {
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();
Expand Down Expand Up @@ -855,4 +864,20 @@ public Savepoint getBooking(@PathVariable Long booking) {
}
}

interface HelloInterface {

@GetMapping("/hello/{name}")
ResponseEntity<String> get(@PathVariable String name);
}

@Controller
static class HelloController implements HelloInterface {

@Override
public ResponseEntity<String> get(String name) {
return ResponseEntity.ok("Hello " + name);
}

}

}

0 comments on commit 4d34444

Please sign in to comment.