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 f217e911b87..fbf4bbcb1d9 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 @@ -253,10 +253,8 @@ public class MvcUriComponentsBuilder { * controller.getAddressesForCountry("US") * builder = MvcUriComponentsBuilder.fromMethodCall(controller); * - * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. - * * @param info either the value returned from a "mock" controller * invocation or the "mock" controller itself after an invocation * @return a UriComponents instance @@ -610,7 +608,11 @@ public class MvcUriComponentsBuilder { @SuppressWarnings("unchecked") private static T initProxy(Class type, ControllerMethodInvocationInterceptor interceptor) { - if (type.isInterface()) { + if (type == Object.class) { + return (T) interceptor; + } + + else if (type.isInterface()) { ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE); factory.addInterface(type); factory.addInterface(MethodInvocationInfo.class); @@ -709,8 +711,18 @@ public class MvcUriComponentsBuilder { } + public interface MethodInvocationInfo { + + Class getControllerType(); + + Method getControllerMethod(); + + Object[] getArgumentValues(); + } + + private static class ControllerMethodInvocationInterceptor - implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor { + implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor, MethodInvocationInfo { private final Class controllerType; @@ -727,15 +739,15 @@ public class MvcUriComponentsBuilder { @Override @Nullable public Object intercept(Object obj, Method method, Object[] args, @Nullable MethodProxy proxy) { - if (method.getName().equals("getControllerMethod")) { + if (method.getName().equals("getControllerType")) { + return this.controllerType; + } + else if (method.getName().equals("getControllerMethod")) { return this.controllerMethod; } else if (method.getName().equals("getArgumentValues")) { return this.argumentValues; } - else if (method.getName().equals("getControllerType")) { - return this.controllerType; - } else if (ReflectionUtils.isObjectMethod(method)) { return ReflectionUtils.invokeMethod(method, obj, args); } @@ -743,7 +755,13 @@ public class MvcUriComponentsBuilder { this.controllerMethod = method; this.argumentValues = args; Class returnType = method.getReturnType(); - return (void.class == returnType ? null : returnType.cast(initProxy(returnType, this))); + try { + return (returnType == void.class ? null : returnType.cast(initProxy(returnType, this))); + } + catch (Throwable ex) { + throw new IllegalStateException( + "Failed to create proxy for controller method return type: " + method, ex); + } } } @@ -752,16 +770,23 @@ public class MvcUriComponentsBuilder { public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable { return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null); } - } + @Override + public Class getControllerType() { + return this.controllerType; + } - public interface MethodInvocationInfo { + @Override + public Method getControllerMethod() { + Assert.state(this.controllerMethod != null, "Not initialized yet"); + return this.controllerMethod; + } - Method getControllerMethod(); - - Object[] getArgumentValues(); - - Class getControllerType(); + @Override + public Object[] getArgumentValues() { + Assert.state(this.argumentValues != null, "Not initialized yet"); + return this.argumentValues; + } } 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 5327686c314..c8987f241fb 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -40,6 +40,7 @@ import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockServletContext; import org.springframework.stereotype.Controller; import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -54,17 +55,9 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromController; -import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMethodCall; -import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMethodName; -import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.on; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.*; /** * Unit tests for {@link org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder}. @@ -142,15 +135,15 @@ public class MvcUriComponentsBuilderTests { } @Test - public void testFromMethodNamePathVariable() throws Exception { - UriComponents uriComponents = fromMethodName( - ControllerWithMethods.class, "methodWithPathVariable", new Object[]{"1"}).build(); + public void testFromMethodNamePathVariable() { + UriComponents uriComponents = fromMethodName(ControllerWithMethods.class, + "methodWithPathVariable", "1").build(); assertThat(uriComponents.toUriString(), is("http://localhost/something/1/foo")); } @Test - public void testFromMethodNameTypeLevelPathVariable() throws Exception { + public void testFromMethodNameTypeLevelPathVariable() { this.request.setContextPath("/myapp"); UriComponents uriComponents = fromMethodName( PersonsAddressesController.class, "getAddressesForCountry", "DE").buildAndExpand("1"); @@ -159,7 +152,7 @@ public class MvcUriComponentsBuilderTests { } @Test - public void testFromMethodNameTwoPathVariables() throws Exception { + public void testFromMethodNameTwoPathVariables() { DateTime now = DateTime.now(); UriComponents uriComponents = fromMethodName( ControllerWithMethods.class, "methodWithTwoPathVariables", 1, now).build(); @@ -168,7 +161,7 @@ public class MvcUriComponentsBuilderTests { } @Test - public void testFromMethodNameWithPathVarAndRequestParam() throws Exception { + public void testFromMethodNameWithPathVarAndRequestParam() { UriComponents uriComponents = fromMethodName( ControllerWithMethods.class, "methodForNextPage", "1", 10, 5).build(); @@ -178,59 +171,56 @@ public class MvcUriComponentsBuilderTests { assertThat(queryParams.get("offset"), contains("10")); } - // SPR-12977 - - @Test - public void fromMethodNameWithBridgedMethod() throws Exception { + @Test // SPR-12977 + public void fromMethodNameWithBridgedMethod() { UriComponents uriComponents = fromMethodName(PersonCrudController.class, "get", (long) 42).build(); + assertThat(uriComponents.toUriString(), is("http://localhost/42")); } - // SPR-11391 - - @Test - public void testFromMethodNameTypeLevelPathVariableWithoutArgumentValue() throws Exception { + @Test // SPR-11391 + public void testFromMethodNameTypeLevelPathVariableWithoutArgumentValue() { UriComponents uriComponents = fromMethodName(UserContactController.class, "showCreate", 123).build(); assertThat(uriComponents.getPath(), is("/user/123/contacts/create")); } @Test - public void testFromMethodNameNotMapped() throws Exception { + public void testFromMethodNameNotMapped() { UriComponents uriComponents = fromMethodName(UnmappedController.class, "unmappedMethod").build(); assertThat(uriComponents.toUriString(), is("http://localhost/")); } @Test - public void testFromMethodNameWithCustomBaseUrlViaStaticCall() throws Exception { + public void testFromMethodNameWithCustomBaseUrlViaStaticCall() { UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponents uriComponents = fromMethodName(builder, ControllerWithMethods.class, - "methodWithPathVariable", new Object[] {"1"}).build(); + "methodWithPathVariable", "1").build(); assertEquals("http://example.org:9090/base/something/1/foo", uriComponents.toString()); assertEquals("http://example.org:9090/base", builder.toUriString()); } @Test - public void testFromMethodNameWithCustomBaseUrlViaInstance() throws Exception { + public void testFromMethodNameWithCustomBaseUrlViaInstance() { UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(builder); UriComponents uriComponents = mvcBuilder.withMethodName(ControllerWithMethods.class, - "methodWithPathVariable", new Object[] {"1"}).build(); + "methodWithPathVariable", "1").build(); assertEquals("http://example.org:9090/base/something/1/foo", uriComponents.toString()); assertEquals("http://example.org:9090/base", builder.toUriString()); } @Test - public void testFromMethodNameWithMetaAnnotation() throws Exception { + public void testFromMethodNameWithMetaAnnotation() { UriComponents uriComponents = fromMethodName(MetaAnnotationController.class, "handleInput").build(); assertThat(uriComponents.toUriString(), is("http://localhost/input")); } - @Test // SPR-14405 - public void testFromMappingNameWithOptionalParam() throws Exception { + @Test // SPR-14405 + public void testFromMappingNameWithOptionalParam() { UriComponents uriComponents = fromMethodName(ControllerWithMethods.class, "methodWithOptionalParam", new Object[] {null}).build(); @@ -255,8 +245,8 @@ public class MvcUriComponentsBuilderTests { @Test public void testFromMethodCallWithTypeLevelUriVars() { - UriComponents uriComponents = fromMethodCall(on( - PersonsAddressesController.class).getAddressesForCountry("DE")).buildAndExpand(15); + UriComponents uriComponents = fromMethodCall( + on(PersonsAddressesController.class).getAddressesForCountry("DE")).buildAndExpand(15); assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses/DE")); } @@ -264,8 +254,8 @@ public class MvcUriComponentsBuilderTests { @Test public void testFromMethodCallWithPathVar() { - UriComponents uriComponents = fromMethodCall(on( - ControllerWithMethods.class).methodWithPathVariable("1")).build(); + UriComponents uriComponents = fromMethodCall( + on(ControllerWithMethods.class).methodWithPathVariable("1")).build(); assertThat(uriComponents.toUriString(), startsWith("http://localhost")); assertThat(uriComponents.toUriString(), endsWith("/something/1/foo")); @@ -273,8 +263,8 @@ public class MvcUriComponentsBuilderTests { @Test public void testFromMethodCallWithPathVarAndRequestParams() { - UriComponents uriComponents = fromMethodCall(on( - ControllerWithMethods.class).methodForNextPage("1", 10, 5)).build(); + UriComponents uriComponents = fromMethodCall( + on(ControllerWithMethods.class).methodForNextPage("1", 10, 5)).build(); assertThat(uriComponents.getPath(), is("/something/1/foo")); @@ -285,8 +275,8 @@ public class MvcUriComponentsBuilderTests { @Test public void testFromMethodCallWithPathVarAndMultiValueRequestParams() { - UriComponents uriComponents = fromMethodCall(on( - ControllerWithMethods.class).methodWithMultiValueRequestParams("1", Arrays.asList(3, 7), 5)).build(); + UriComponents uriComponents = fromMethodCall( + on(ControllerWithMethods.class).methodWithMultiValueRequestParams("1", Arrays.asList(3, 7), 5)).build(); assertThat(uriComponents.getPath(), is("/something/1/foo")); @@ -315,7 +305,7 @@ public class MvcUriComponentsBuilderTests { } @Test - public void testFromMappingName() throws Exception { + public void testFromMappingName() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setServletContext(new MockServletContext()); context.register(WebConfig.class); @@ -332,7 +322,7 @@ public class MvcUriComponentsBuilderTests { } @Test - public void testFromMappingNameWithCustomBaseUrl() throws Exception { + public void testFromMappingNameWithCustomBaseUrl() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setServletContext(new MockServletContext()); context.register(WebConfig.class); @@ -370,6 +360,13 @@ public class MvcUriComponentsBuilderTests { assertThat(uriComponents.toUriString(), startsWith("http://barfoo:8888")); } + @Test // SPR-16710 + public void withStringReturnType() { + UriComponents uriComponents = MvcUriComponentsBuilder.fromMethodCall( + on(BookingController.class).getBooking(21L)).buildAndExpand(42); + assertEquals("http://localhost/hotels/42/bookings/21", uriComponents.encode().toUri().toString()); + } + static class Person { @@ -516,4 +513,15 @@ public class MvcUriComponentsBuilderTests { } } + + @Controller + @RequestMapping("/hotels/{hotel}") + public class BookingController { + + @GetMapping("/bookings/{booking}") + public Object getBooking(@PathVariable Long booking) { + return "url"; + } + } + }