Browse Source

Revised reference example for linkable controller method signature

Issue: SPR-16710
pull/1776/merge
Juergen Hoeller 8 years ago
parent
commit
7ee6130680
  1. 188
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java
  2. 15
      src/docs/asciidoc/web/webmvc.adoc

188
spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilderTests.java

@ -50,6 +50,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponents;
@ -85,38 +86,38 @@ public class MvcUriComponentsBuilderTests {
@Test @Test
public void testFromController() { public void fromControllerPlain() {
UriComponents uriComponents = fromController(PersonControllerImpl.class).build(); UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
assertThat(uriComponents.toUriString(), Matchers.endsWith("/people")); assertThat(uriComponents.toUriString(), Matchers.endsWith("/people"));
} }
@Test @Test
public void testFromControllerUriTemplate() { public void fromControllerUriTemplate() {
UriComponents uriComponents = fromController(PersonsAddressesController.class).buildAndExpand(15); UriComponents uriComponents = fromController(PersonsAddressesController.class).buildAndExpand(15);
assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses")); assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses"));
} }
@Test @Test
public void testFromControllerSubResource() { public void fromControllerSubResource() {
UriComponents uriComponents = fromController(PersonControllerImpl.class).pathSegment("something").build(); UriComponents uriComponents = fromController(PersonControllerImpl.class).pathSegment("something").build();
assertThat(uriComponents.toUriString(), endsWith("/people/something")); assertThat(uriComponents.toUriString(), endsWith("/people/something"));
} }
@Test @Test
public void testFromControllerTwoTypeLevelMappings() { public void fromControllerTwoTypeLevelMappings() {
UriComponents uriComponents = fromController(InvalidController.class).build(); UriComponents uriComponents = fromController(InvalidController.class).build();
assertThat(uriComponents.toUriString(), is("http://localhost/persons")); assertThat(uriComponents.toUriString(), is("http://localhost/persons"));
} }
@Test @Test
public void testFromControllerNotMapped() { public void fromControllerNotMapped() {
UriComponents uriComponents = fromController(UnmappedController.class).build(); UriComponents uriComponents = fromController(UnmappedController.class).build();
assertThat(uriComponents.toUriString(), is("http://localhost/")); assertThat(uriComponents.toUriString(), is("http://localhost/"));
} }
@Test @Test
public void testFromControllerWithCustomBaseUrlViaStaticCall() { public void fromControllerWithCustomBaseUrlViaStaticCall() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
UriComponents uriComponents = fromController(builder, PersonControllerImpl.class).build(); UriComponents uriComponents = fromController(builder, PersonControllerImpl.class).build();
@ -125,9 +126,9 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromControllerWithCustomBaseUrlViaInstance() { public void fromControllerWithCustomBaseUrlViaInstance() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(builder); MvcUriComponentsBuilder mvcBuilder = relativeTo(builder);
UriComponents uriComponents = mvcBuilder.withController(PersonControllerImpl.class).build(); UriComponents uriComponents = mvcBuilder.withController(PersonControllerImpl.class).build();
assertEquals("http://example.org:9090/base/people", uriComponents.toString()); assertEquals("http://example.org:9090/base/people", uriComponents.toString());
@ -135,7 +136,31 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodNamePathVariable() { public void usesForwardedHostAsHostIfHeaderIsSet() {
this.request.addHeader("X-Forwarded-Host", "somethingDifferent");
UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
assertThat(uriComponents.toUriString(), startsWith("http://somethingDifferent"));
}
@Test
public void usesForwardedHostAndPortFromHeader() {
request.addHeader("X-Forwarded-Host", "foobar:8088");
UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
assertThat(uriComponents.toUriString(), startsWith("http://foobar:8088"));
}
@Test
public void usesFirstHostOfXForwardedHost() {
request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088");
UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
assertThat(uriComponents.toUriString(), startsWith("http://barfoo:8888"));
}
@Test
public void fromMethodNamePathVariable() {
UriComponents uriComponents = fromMethodName(ControllerWithMethods.class, UriComponents uriComponents = fromMethodName(ControllerWithMethods.class,
"methodWithPathVariable", "1").build(); "methodWithPathVariable", "1").build();
@ -143,7 +168,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodNameTypeLevelPathVariable() { public void fromMethodNameTypeLevelPathVariable() {
this.request.setContextPath("/myapp"); this.request.setContextPath("/myapp");
UriComponents uriComponents = fromMethodName( UriComponents uriComponents = fromMethodName(
PersonsAddressesController.class, "getAddressesForCountry", "DE").buildAndExpand("1"); PersonsAddressesController.class, "getAddressesForCountry", "DE").buildAndExpand("1");
@ -152,7 +177,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodNameTwoPathVariables() { public void fromMethodNameTwoPathVariables() {
DateTime now = DateTime.now(); DateTime now = DateTime.now();
UriComponents uriComponents = fromMethodName( UriComponents uriComponents = fromMethodName(
ControllerWithMethods.class, "methodWithTwoPathVariables", 1, now).build(); ControllerWithMethods.class, "methodWithTwoPathVariables", 1, now).build();
@ -161,7 +186,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodNameWithPathVarAndRequestParam() { public void fromMethodNameWithPathVarAndRequestParam() {
UriComponents uriComponents = fromMethodName( UriComponents uriComponents = fromMethodName(
ControllerWithMethods.class, "methodForNextPage", "1", 10, 5).build(); ControllerWithMethods.class, "methodForNextPage", "1", 10, 5).build();
@ -179,21 +204,21 @@ public class MvcUriComponentsBuilderTests {
} }
@Test // SPR-11391 @Test // SPR-11391
public void testFromMethodNameTypeLevelPathVariableWithoutArgumentValue() { public void fromMethodNameTypeLevelPathVariableWithoutArgumentValue() {
UriComponents uriComponents = fromMethodName(UserContactController.class, "showCreate", 123).build(); UriComponents uriComponents = fromMethodName(UserContactController.class, "showCreate", 123).build();
assertThat(uriComponents.getPath(), is("/user/123/contacts/create")); assertThat(uriComponents.getPath(), is("/user/123/contacts/create"));
} }
@Test @Test
public void testFromMethodNameNotMapped() { public void fromMethodNameNotMapped() {
UriComponents uriComponents = fromMethodName(UnmappedController.class, "unmappedMethod").build(); UriComponents uriComponents = fromMethodName(UnmappedController.class, "unmappedMethod").build();
assertThat(uriComponents.toUriString(), is("http://localhost/")); assertThat(uriComponents.toUriString(), is("http://localhost/"));
} }
@Test @Test
public void testFromMethodNameWithCustomBaseUrlViaStaticCall() { public void fromMethodNameWithCustomBaseUrlViaStaticCall() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
UriComponents uriComponents = fromMethodName(builder, ControllerWithMethods.class, UriComponents uriComponents = fromMethodName(builder, ControllerWithMethods.class,
"methodWithPathVariable", "1").build(); "methodWithPathVariable", "1").build();
@ -203,9 +228,9 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodNameWithCustomBaseUrlViaInstance() { public void fromMethodNameWithCustomBaseUrlViaInstance() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(builder); MvcUriComponentsBuilder mvcBuilder = relativeTo(builder);
UriComponents uriComponents = mvcBuilder.withMethodName(ControllerWithMethods.class, UriComponents uriComponents = mvcBuilder.withMethodName(ControllerWithMethods.class,
"methodWithPathVariable", "1").build(); "methodWithPathVariable", "1").build();
@ -213,14 +238,8 @@ public class MvcUriComponentsBuilderTests {
assertEquals("http://example.org:9090/base", builder.toUriString()); assertEquals("http://example.org:9090/base", builder.toUriString());
} }
@Test
public void testFromMethodNameWithMetaAnnotation() {
UriComponents uriComponents = fromMethodName(MetaAnnotationController.class, "handleInput").build();
assertThat(uriComponents.toUriString(), is("http://localhost/input"));
}
@Test // SPR-14405 @Test // SPR-14405
public void testFromMappingNameWithOptionalParam() { public void fromMethodNameWithOptionalParam() {
UriComponents uriComponents = fromMethodName(ControllerWithMethods.class, UriComponents uriComponents = fromMethodName(ControllerWithMethods.class,
"methodWithOptionalParam", new Object[] {null}).build(); "methodWithOptionalParam", new Object[] {null}).build();
@ -228,7 +247,14 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCall() { public void fromMethodNameWithMetaAnnotation() {
UriComponents uriComponents = fromMethodName(MetaAnnotationController.class, "handleInput").build();
assertThat(uriComponents.toUriString(), is("http://localhost/input"));
}
@Test
public void fromMethodCallPlain() {
UriComponents uriComponents = fromMethodCall(on(ControllerWithMethods.class).myMethod(null)).build(); UriComponents uriComponents = fromMethodCall(on(ControllerWithMethods.class).myMethod(null)).build();
assertThat(uriComponents.toUriString(), startsWith("http://localhost")); assertThat(uriComponents.toUriString(), startsWith("http://localhost"));
@ -236,7 +262,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCallOnSubclass() { public void fromMethodCallOnSubclass() {
UriComponents uriComponents = fromMethodCall(on(ExtendedController.class).myMethod(null)).build(); UriComponents uriComponents = fromMethodCall(on(ExtendedController.class).myMethod(null)).build();
assertThat(uriComponents.toUriString(), startsWith("http://localhost")); assertThat(uriComponents.toUriString(), startsWith("http://localhost"));
@ -244,16 +270,15 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCallWithTypeLevelUriVars() { public void fromMethodCallWithTypeLevelUriVars() {
UriComponents uriComponents = fromMethodCall( UriComponents uriComponents = fromMethodCall(
on(PersonsAddressesController.class).getAddressesForCountry("DE")).buildAndExpand(15); on(PersonsAddressesController.class).getAddressesForCountry("DE")).buildAndExpand(15);
assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses/DE")); assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses/DE"));
} }
@Test @Test
public void testFromMethodCallWithPathVar() { public void fromMethodCallWithPathVariable() {
UriComponents uriComponents = fromMethodCall( UriComponents uriComponents = fromMethodCall(
on(ControllerWithMethods.class).methodWithPathVariable("1")).build(); on(ControllerWithMethods.class).methodWithPathVariable("1")).build();
@ -262,7 +287,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCallWithPathVarAndRequestParams() { public void fromMethodCallWithPathVariableAndRequestParams() {
UriComponents uriComponents = fromMethodCall( UriComponents uriComponents = fromMethodCall(
on(ControllerWithMethods.class).methodForNextPage("1", 10, 5)).build(); on(ControllerWithMethods.class).methodForNextPage("1", 10, 5)).build();
@ -274,7 +299,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCallWithPathVarAndMultiValueRequestParams() { public void fromMethodCallWithPathVariableAndMultiValueRequestParams() {
UriComponents uriComponents = fromMethodCall( UriComponents uriComponents = fromMethodCall(
on(ControllerWithMethods.class).methodWithMultiValueRequestParams("1", Arrays.asList(3, 7), 5)).build(); on(ControllerWithMethods.class).methodWithMultiValueRequestParams("1", Arrays.asList(3, 7), 5)).build();
@ -286,7 +311,7 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCallWithCustomBaseUrlViaStaticCall() { public void fromMethodCallWithCustomBaseUrlViaStaticCall() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
UriComponents uriComponents = fromMethodCall(builder, on(ControllerWithMethods.class).myMethod(null)).build(); UriComponents uriComponents = fromMethodCall(builder, on(ControllerWithMethods.class).myMethod(null)).build();
@ -295,17 +320,49 @@ public class MvcUriComponentsBuilderTests {
} }
@Test @Test
public void testFromMethodCallWithCustomBaseUrlViaInstance() { public void fromMethodCallWithCustomBaseUrlViaInstance() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(builder); MvcUriComponentsBuilder mvcBuilder = relativeTo(builder);
UriComponents result = mvcBuilder.withMethodCall(on(ControllerWithMethods.class).myMethod(null)).build(); UriComponents result = mvcBuilder.withMethodCall(on(ControllerWithMethods.class).myMethod(null)).build();
assertEquals("http://example.org:9090/base/something/else", result.toString()); assertEquals("http://example.org:9090/base/something/else", result.toString());
assertEquals("http://example.org:9090/base", builder.toUriString()); assertEquals("http://example.org:9090/base", builder.toUriString());
} }
@Test // SPR-16710
public void fromMethodCallWithModelAndViewReturnType() {
UriComponents uriComponents = fromMethodCall(
on(BookingControllerWithModelAndView.class).getBooking(21L)).buildAndExpand(42);
assertEquals("http://localhost/hotels/42/bookings/21", uriComponents.encode().toUri().toString());
}
@Test // SPR-16710
public void fromMethodCallWithObjectReturnType() {
UriComponents uriComponents = fromMethodCall(
on(BookingControllerWithObject.class).getBooking(21L)).buildAndExpand(42);
assertEquals("http://localhost/hotels/42/bookings/21", uriComponents.encode().toUri().toString());
}
@Test(expected = IllegalStateException.class) // SPR-16710
public void fromMethodCallWithStringReturnType() {
UriComponents uriComponents = fromMethodCall(
on(BookingControllerWithString.class).getBooking(21L)).buildAndExpand(42);
assertEquals("http://localhost/hotels/42/bookings/21", uriComponents.encode().toUri().toString());
}
@Test // SPR-16710
public void fromMethodNameWithStringReturnType() {
UriComponents uriComponents = fromMethodName(
BookingControllerWithString.class, "getBooking", 21L).buildAndExpand(42);
assertEquals("http://localhost/hotels/42/bookings/21", uriComponents.encode().toUri().toString());
}
@Test @Test
public void testFromMappingName() { public void fromMappingNamePlain() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext()); context.setServletContext(new MockServletContext());
context.register(WebConfig.class); context.register(WebConfig.class);
@ -317,12 +374,12 @@ public class MvcUriComponentsBuilderTests {
this.request.setContextPath("/base"); this.request.setContextPath("/base");
String mappingName = "PAC#getAddressesForCountry"; String mappingName = "PAC#getAddressesForCountry";
String url = MvcUriComponentsBuilder.fromMappingName(mappingName).arg(0, "DE").buildAndExpand(123); String url = fromMappingName(mappingName).arg(0, "DE").buildAndExpand(123);
assertEquals("/base/people/123/addresses/DE", url); assertEquals("/base/people/123/addresses/DE", url);
} }
@Test @Test
public void testFromMappingNameWithCustomBaseUrl() { public void fromMappingNameWithCustomBaseUrl() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext()); context.setServletContext(new MockServletContext());
context.register(WebConfig.class); context.register(WebConfig.class);
@ -331,42 +388,11 @@ public class MvcUriComponentsBuilderTests {
this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
UriComponentsBuilder baseUrl = UriComponentsBuilder.fromUriString("http://example.org:9999/base"); UriComponentsBuilder baseUrl = UriComponentsBuilder.fromUriString("http://example.org:9999/base");
MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(baseUrl); MvcUriComponentsBuilder mvcBuilder = relativeTo(baseUrl);
String url = mvcBuilder.withMappingName("PAC#getAddressesForCountry").arg(0, "DE").buildAndExpand(123); String url = mvcBuilder.withMappingName("PAC#getAddressesForCountry").arg(0, "DE").buildAndExpand(123);
assertEquals("http://example.org:9999/base/people/123/addresses/DE", url); assertEquals("http://example.org:9999/base/people/123/addresses/DE", url);
} }
@Test
public void usesForwardedHostAsHostIfHeaderIsSet() {
this.request.addHeader("X-Forwarded-Host", "somethingDifferent");
UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
assertThat(uriComponents.toUriString(), startsWith("http://somethingDifferent"));
}
@Test
public void usesForwardedHostAndPortFromHeader() {
request.addHeader("X-Forwarded-Host", "foobar:8088");
UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
assertThat(uriComponents.toUriString(), startsWith("http://foobar:8088"));
}
@Test
public void usesFirstHostOfXForwardedHost() {
request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088");
UriComponents uriComponents = fromController(PersonControllerImpl.class).build();
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 { static class Person {
@ -516,7 +542,18 @@ public class MvcUriComponentsBuilderTests {
@Controller @Controller
@RequestMapping("/hotels/{hotel}") @RequestMapping("/hotels/{hotel}")
public class BookingController { static class BookingControllerWithModelAndView {
@GetMapping("/bookings/{booking}")
public ModelAndView getBooking(@PathVariable Long booking) {
return new ModelAndView("url");
}
}
@Controller
@RequestMapping("/hotels/{hotel}")
static class BookingControllerWithObject {
@GetMapping("/bookings/{booking}") @GetMapping("/bookings/{booking}")
public Object getBooking(@PathVariable Long booking) { public Object getBooking(@PathVariable Long booking) {
@ -524,4 +561,15 @@ public class MvcUriComponentsBuilderTests {
} }
} }
@Controller
@RequestMapping("/hotels/{hotel}")
static class BookingControllerWithString {
@GetMapping("/bookings/{booking}")
public String getBooking(@PathVariable Long booking) {
return "url";
}
}
} }

15
src/docs/asciidoc/web/webmvc.adoc

@ -3098,7 +3098,8 @@ per request, and also provides an option to remove and ignore such headers.
[[mvc-links-to-controllers]] [[mvc-links-to-controllers]]
=== Links to controllers === Links to controllers
Spring MVC provides a mechanism to prepare links to controller methods. For example: Spring MVC provides a mechanism to prepare links to controller methods. For example,
the following MVC controller easily allows for link creation:
[source,java,indent=0] [source,java,indent=0]
[subs="verbatim,quotes"] [subs="verbatim,quotes"]
@ -3108,7 +3109,7 @@ Spring MVC provides a mechanism to prepare links to controller methods. For exam
public class BookingController { public class BookingController {
@GetMapping("/bookings/{booking}") @GetMapping("/bookings/{booking}")
public String getBooking(@PathVariable Long booking) { public ModelAndView getBooking(@PathVariable Long booking) {
// ... // ...
} }
} }
@ -3145,6 +3146,16 @@ akin to mock testing through proxies to avoid referring to the controller method
URI uri = uriComponents.encode().toUri(); URI uri = uriComponents.encode().toUri();
---- ----
[NOTE]
====
Controller method signatures are limited in their design when supposed to be usable for
link creation with `fromMethodCall`. Aside from needing a proper parameter signature,
there is a technical limitation on the return type: namely generating a runtime proxy
for link builder invocations, so the return type must not be `final`. In particular,
the common `String` return type for view names does not work here; use `ModelAndView`
or even plain `Object` (with a `String` return value) instead.
====
The above examples use static methods in `MvcUriComponentsBuilder`. Internally they rely The above examples use static methods in `MvcUriComponentsBuilder`. Internally they rely
on `ServletUriComponentsBuilder` to prepare a base URL from the scheme, host, port, on `ServletUriComponentsBuilder` to prepare a base URL from the scheme, host, port,
context path and servlet path of the current request. This works well in most cases, context path and servlet path of the current request. This works well in most cases,

Loading…
Cancel
Save