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 058cbb126d..7b8a7c271f 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
@@ -64,8 +64,19 @@ import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
- * A UriComponentsBuilder that helps to build URIs to Spring MVC controllers
- * and methods from their request mappings.
+ * Creates instances of {@link org.springframework.web.util.UriComponentsBuilder}
+ * by pointing to Spring MVC controllers and {@code @RequestMapping} methods.
+ *
+ *
The static {@code fromXxx(...)} methods prepare links relative to the
+ * current request as determined by a call to
+ * {@link org.springframework.web.servlet.support.ServletUriComponentsBuilder#fromCurrentServletMapping()}.
+ *
+ *
The static {@code fromXxx(UriComponentsBuilder,...)} methods can be given
+ * the baseUrl when operating outside the context of a request.
+ *
+ *
You can also create an MvcUriComponentsBuilder instance with a baseUrl
+ * via {@link #relativeTo(org.springframework.web.util.UriComponentsBuilder)}
+ * and then use the non-static {@code withXxx(...)} method variants.
*
* @author Oliver Gierke
* @author Rossen Stoyanchev
@@ -94,6 +105,8 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
new PathVariableMethodArgumentResolver(), new RequestParamMethodArgumentResolver(false));
}
+ private final UriComponentsBuilder baseUrl;
+
/**
* Default constructor. Protected to prevent direct instantiation.
@@ -104,7 +117,9 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
* @see #fromMappingName(String)
* @see #fromMethod(java.lang.reflect.Method, Object...)
*/
- protected MvcUriComponentsBuilder() {
+ protected MvcUriComponentsBuilder(UriComponentsBuilder baseUrl) {
+ Assert.notNull(baseUrl, "'baseUrl' is required");
+ this.baseUrl = baseUrl;
}
/**
@@ -113,8 +128,19 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
*/
protected MvcUriComponentsBuilder(MvcUriComponentsBuilder other) {
super(other);
+ this.baseUrl = other.baseUrl;
}
+ /**
+ * Create an instance of this class with a base URL. After that calls to one
+ * of the instance based {@code withXxx(...}} methods will create URLs relative
+ * to the given base URL.
+ */
+ public static MvcUriComponentsBuilder relativeTo(UriComponentsBuilder baseUrl) {
+ return new MvcUriComponentsBuilder(baseUrl);
+ }
+
+
/**
* Create a {@link UriComponentsBuilder} from the mapping of a controller class
* and current request information including Servlet mapping. If the controller
@@ -316,6 +342,23 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
* @since 4.1
*/
public static MethodArgumentBuilder fromMappingName(String mappingName) {
+ return fromMappingName(null, mappingName);
+ }
+
+ /**
+ * An alternative to {@link #fromMappingName(String)} that accepts a
+ * {@code UriComponentsBuilder} representing the base URL. This is useful
+ * when using MvcUriComponentsBuilder outside the context of processing a
+ * request or to apply a custom baseUrl not matching the current request.
+ * @param builder the builder for the base URL; the builder will be cloned
+ * and therefore not modified and may be re-used for further calls.
+ * @param mappingName the mapping name
+ * @return a builder to to prepare the URI String
+ * @throws IllegalArgumentException if the mapping name is not found or
+ * if there is no unique match
+ * @since 4.2
+ */
+ public static MethodArgumentBuilder fromMappingName(UriComponentsBuilder builder, String mappingName) {
RequestMappingInfoHandlerMapping handlerMapping = getRequestMappingInfoHandlerMapping();
List handlerMethods = handlerMapping.getHandlerMethodsForMappingName(mappingName);
if (handlerMethods == null) {
@@ -325,7 +368,7 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
throw new IllegalArgumentException(
"No unique match for mapping mappingName " + mappingName + ": " + handlerMethods);
}
- return new MethodArgumentBuilder(handlerMethods.get(0).getMethod());
+ return new MethodArgumentBuilder(builder, handlerMethods.get(0).getMethod());
}
/**
@@ -530,6 +573,46 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
}
}
+ /**
+ * An alternative to {@link #fromController(Class)} for use with an instance
+ * of this class created via a call to {@link #relativeTo}.
+ */
+ public UriComponentsBuilder withController(Class> controllerType) {
+ return fromController(this.baseUrl, controllerType);
+ }
+
+ /**
+ * An alternative to {@link #fromMethodName(Class, String, Object...)}} for
+ * use with an instance of this class created via {@link #relativeTo}.
+ */
+ public UriComponentsBuilder withMethodName(Class> controllerType, String methodName, Object... args) {
+ return fromMethodName(this.baseUrl, controllerType, methodName, args);
+ }
+
+ /**
+ * An alternative to {@link #fromMethodCall(Object)} for use with an instance
+ * of this class created via {@link #relativeTo}.
+ */
+ public UriComponentsBuilder withMethodCall(Object invocationInfo) {
+ return fromMethodCall(this.baseUrl, invocationInfo);
+ }
+
+ /**
+ * An alternative to {@link #fromMappingName(String)} for use with an instance
+ * of this class created via {@link #relativeTo}.
+ */
+ public MethodArgumentBuilder withMappingName(String mappingName) {
+ return fromMappingName(this.baseUrl, mappingName);
+ }
+
+ /**
+ * An alternative to {@link #fromMethod(java.lang.reflect.Method, Object...)}
+ * for use with an instance of this class created via {@link #relativeTo}.
+ */
+ public UriComponentsBuilder withMethod(Method method, Object... args) {
+ return fromMethod(this.baseUrl, method, args);
+ }
+
@Override
public Object clone() {
return new MvcUriComponentsBuilder(this);
@@ -590,9 +673,16 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
private final Object[] argumentValues;
+ private final UriComponentsBuilder baseUrl;
+
public MethodArgumentBuilder(Method method) {
+ this(null, method);
+ }
+
+ public MethodArgumentBuilder(UriComponentsBuilder baseUrl, Method method) {
Assert.notNull(method, "'method' is required");
+ this.baseUrl = baseUrl;
this.method = method;
this.argumentValues = new Object[method.getParameterTypes().length];
for (int i = 0; i < this.argumentValues.length; i++) {
@@ -606,12 +696,12 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
}
public String build() {
- return MvcUriComponentsBuilder.fromMethod(this.method, this.argumentValues)
+ return MvcUriComponentsBuilder.fromMethod(this.baseUrl, this.method, this.argumentValues)
.build(false).encode().toUriString();
}
public String buildAndExpand(Object... uriVariables) {
- return MvcUriComponentsBuilder.fromMethod(this.method, this.argumentValues)
+ return MvcUriComponentsBuilder.fromMethod(this.baseUrl, this.method, this.argumentValues)
.build(false).expand(uriVariables).encode().toString();
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/UriComponentsBuilderMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/UriComponentsBuilderMethodArgumentResolver.java
index 74e632f2a9..d01f2e8cb7 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/UriComponentsBuilderMethodArgumentResolver.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/UriComponentsBuilderMethodArgumentResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2015 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.
@@ -26,6 +26,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
+
/**
* Resolvers argument values of type {@link UriComponentsBuilder}.
*
@@ -37,9 +38,11 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {
+
@Override
public boolean supportsParameter(MethodParameter parameter) {
- return UriComponentsBuilder.class.isAssignableFrom(parameter.getParameterType());
+ Class> type = parameter.getParameterType();
+ return (UriComponentsBuilder.class.equals(type) || ServletUriComponentsBuilder.class.equals(type));
}
@Override
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 416c90501c..e2b7d3c373 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
@@ -30,10 +30,12 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.springframework.context.annotation.Bean;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.http.HttpEntity;
import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockServletContext;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@@ -41,6 +43,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@@ -100,7 +106,7 @@ public class MvcUriComponentsBuilderTests {
}
@Test
- public void testFromControllerWithCustomBaseUrl() {
+ public void testFromControllerWithCustomBaseUrlViaStaticCall() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
UriComponents uriComponents = fromController(builder, PersonControllerImpl.class).build();
@@ -108,6 +114,16 @@ public class MvcUriComponentsBuilderTests {
assertEquals("http://example.org:9090/base", builder.toUriString());
}
+ @Test
+ public void testFromControllerWithCustomBaseUrlViaInstance() {
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
+ MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(builder);
+ UriComponents uriComponents = mvcBuilder.withController(PersonControllerImpl.class).build();
+
+ assertEquals("http://example.org:9090/base/people", uriComponents.toString());
+ assertEquals("http://example.org:9090/base", builder.toUriString());
+ }
+
@Test
public void testFromMethodNamePathVariable() throws Exception {
UriComponents uriComponents = fromMethodName(
@@ -162,7 +178,7 @@ public class MvcUriComponentsBuilderTests {
}
@Test
- public void testFromMethodNameWithCustomBaseUrl() throws Exception {
+ public void testFromMethodNameWithCustomBaseUrlViaStaticCall() throws Exception {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
UriComponents uriComponents = fromMethodName(builder, ControllerWithMethods.class,
"methodWithPathVariable", new Object[] {"1"}).build();
@@ -171,6 +187,17 @@ public class MvcUriComponentsBuilderTests {
assertEquals("http://example.org:9090/base", builder.toUriString());
}
+ @Test
+ public void testFromMethodNameWithCustomBaseUrlViaInstance() throws Exception {
+ 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();
+
+ assertEquals("http://example.org:9090/base/something/1/foo", uriComponents.toString());
+ assertEquals("http://example.org:9090/base", builder.toUriString());
+ }
+
@Test
public void testFromMethodCall() {
UriComponents uriComponents = fromMethodCall(on(ControllerWithMethods.class).myMethod(null)).build();
@@ -222,7 +249,7 @@ public class MvcUriComponentsBuilderTests {
}
@Test
- public void testFromMethodCallWithCustomBaseUrl() {
+ public void testFromMethodCallWithCustomBaseUrlViaStaticCall() {
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
UriComponents uriComponents = fromMethodCall(builder, on(ControllerWithMethods.class).myMethod(null)).build();
@@ -230,6 +257,48 @@ public class MvcUriComponentsBuilderTests {
assertEquals("http://example.org:9090/base", builder.toUriString());
}
+ @Test
+ public void testFromMethodCallWithCustomBaseUrlViaInstance() {
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.org:9090/base");
+ MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(builder);
+ 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", builder.toUriString());
+ }
+
+ @Test
+ public void testFromMappingName() throws Exception {
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+ context.setServletContext(new MockServletContext());
+ context.register(WebConfig.class);
+ context.refresh();
+
+ this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
+ this.request.setServerName("example.org");
+ this.request.setServerPort(9999);
+ this.request.setContextPath("/base");
+
+ String mappingName = "PAC#getAddressesForCountry";
+ String url = MvcUriComponentsBuilder.fromMappingName(mappingName).arg(0, "DE").buildAndExpand(123);
+ assertEquals("http://example.org:9999/base/people/123/addresses/DE", url);
+ }
+
+ @Test
+ public void testFromMappingNameWithCustomBaseUrl() throws Exception {
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+ context.setServletContext(new MockServletContext());
+ context.register(WebConfig.class);
+ context.refresh();
+
+ this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
+
+ UriComponentsBuilder baseUrl = UriComponentsBuilder.fromUriString("http://example.org:9999/base");
+ MvcUriComponentsBuilder mvcBuilder = MvcUriComponentsBuilder.relativeTo(baseUrl);
+ String url = mvcBuilder.withMappingName("PAC#getAddressesForCountry").arg(0, "DE").buildAndExpand(123);
+ assertEquals("http://example.org:9999/base/people/123/addresses/DE", url);
+ }
+
@Test
public void usesForwardedHostAsHostIfHeaderIsSet() {
this.request.addHeader("X-Forwarded-Host", "somethingDifferent");
@@ -329,6 +398,7 @@ public class MvcUriComponentsBuilderTests {
}
}
+ @SuppressWarnings("unused")
@RequestMapping("/user/{userId}/contacts")
static class UserContactController {
@@ -338,4 +408,14 @@ public class MvcUriComponentsBuilderTests {
}
}
+ @EnableWebMvc
+ static class WebConfig extends WebMvcConfigurerAdapter {
+
+ @Bean
+ public PersonsAddressesController controller() {
+ return new PersonsAddressesController();
+ }
+
+ }
+
}