MvcUriComponentsBuilder is aware of path prefixes

Issue: SPR-16336
This commit is contained in:
Rossen Stoyanchev
2018-06-14 22:05:53 -04:00
parent e6fef9555d
commit 58cce615f5
3 changed files with 108 additions and 31 deletions

View File

@@ -174,8 +174,15 @@ public class MvcUriComponentsBuilder {
Class<?> controllerType) {
builder = getBaseUrlToUse(builder);
// Externally configured prefix via PathConfigurer..
String prefix = getPathPrefix(controllerType);
builder.path(prefix);
String mapping = getClassMapping(controllerType);
return builder.path(mapping);
builder.path(mapping);
return builder;
}
/**
@@ -526,15 +533,21 @@ public class MvcUriComponentsBuilder {
}
private static UriComponentsBuilder fromMethodInternal(@Nullable UriComponentsBuilder baseUrl,
private static UriComponentsBuilder fromMethodInternal(@Nullable UriComponentsBuilder builder,
Class<?> controllerType, Method method, Object... args) {
baseUrl = getBaseUrlToUse(baseUrl);
builder = getBaseUrlToUse(builder);
// Externally configured prefix via PathConfigurer..
String prefix = getPathPrefix(controllerType);
builder.path(prefix);
String typePath = getClassMapping(controllerType);
String methodPath = getMethodMapping(method);
String path = pathMatcher.combine(typePath, methodPath);
baseUrl.path(path);
UriComponents uriComponents = applyContributors(baseUrl, method, args);
builder.path(path);
UriComponents uriComponents = applyContributors(builder, method, args);
return UriComponentsBuilder.newInstance().uriComponents(uriComponents);
}
@@ -544,6 +557,22 @@ public class MvcUriComponentsBuilder {
baseUrl.cloneBuilder();
}
private static String getPathPrefix(Class<?> controllerType) {
WebApplicationContext wac = getWebApplicationContext();
if (wac != null) {
Map<String, RequestMappingHandlerMapping> map = wac.getBeansOfType(RequestMappingHandlerMapping.class);
for (RequestMappingHandlerMapping mapping : map.values()) {
if (mapping.isHandler(controllerType)) {
String prefix = mapping.getPathPrefix(controllerType);
if (prefix != null) {
return prefix;
}
}
}
}
return "";
}
private static String getClassMapping(Class<?> controllerType) {
Assert.notNull(controllerType, "'controllerType' must not be null");
RequestMapping mapping = AnnotatedElementUtils.findMergedAnnotation(controllerType, RequestMapping.class);

View File

@@ -32,7 +32,6 @@ import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.CrossOrigin;
@@ -67,7 +66,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private boolean useTrailingSlashMatch = true;
private final Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
private Map<String, Predicate<Class<?>>> pathPrefixes = new LinkedHashMap<>();
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
@@ -120,10 +119,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @since 5.1
*/
public void setPathPrefixes(Map<String, Predicate<Class<?>>> prefixes) {
this.pathPrefixes.clear();
prefixes.entrySet().stream()
.filter(entry -> StringUtils.hasText(entry.getKey()))
.forEach(entry -> this.pathPrefixes.put(entry.getKey(), entry.getValue()));
this.pathPrefixes = Collections.unmodifiableMap(new LinkedHashMap<>(prefixes));
}
/**
@@ -180,7 +176,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
* @since 5.1
*/
public Map<String, Predicate<Class<?>>> getPathPrefixes() {
return Collections.unmodifiableMap(this.pathPrefixes);
return this.pathPrefixes;
}
/**
@@ -227,20 +223,28 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
if (typeInfo != null) {
info = typeInfo.combine(info);
}
for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) {
if (entry.getValue().test(handlerType)) {
String prefix = entry.getKey();
if (this.embeddedValueResolver != null) {
prefix = this.embeddedValueResolver.resolveStringValue(prefix);
}
info = RequestMappingInfo.paths(prefix).build().combine(info);
break;
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).build().combine(info);
}
}
return info;
}
@Nullable
String getPathPrefix(Class<?> handlerType) {
for (Map.Entry<String, Predicate<Class<?>>> entry : this.pathPrefixes.entrySet()) {
if (entry.getValue().test(handlerType)) {
String prefix = entry.getKey();
if (this.embeddedValueResolver != null) {
prefix = this.embeddedValueResolver.resolveStringValue(prefix);
}
return prefix;
}
}
return null;
}
/**
* Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},
* supplying the appropriate custom {@link RequestCondition} depending on whether

View File

@@ -56,6 +56,7 @@ import org.springframework.web.filter.ForwardedHeaderFilter;
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.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@@ -378,12 +379,9 @@ public class MvcUriComponentsBuilderTests {
@Test
public void fromMappingNamePlain() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(WebConfig.class);
context.refresh();
this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
initWebApplicationContext(WebConfig.class);
this.request.setServerName("example.org");
this.request.setServerPort(9999);
this.request.setContextPath("/base");
@@ -395,12 +393,8 @@ public class MvcUriComponentsBuilderTests {
@Test
public void fromMappingNameWithCustomBaseUrl() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(WebConfig.class);
context.refresh();
this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
initWebApplicationContext(WebConfig.class);
UriComponentsBuilder baseUrl = UriComponentsBuilder.fromUriString("http://example.org:9999/base");
MvcUriComponentsBuilder mvcBuilder = relativeTo(baseUrl);
@@ -408,6 +402,41 @@ public class MvcUriComponentsBuilderTests {
assertEquals("http://example.org:9999/base/people/123/addresses/DE", url);
}
@Test
public void fromControllerWithPrefix() {
initWebApplicationContext(PathPrefixWebConfig.class);
this.request.setServerName("example.org");
this.request.setServerPort(9999);
this.request.setContextPath("/base");
assertEquals("http://example.org:9999/base/api/people/123/addresses",
fromController(PersonsAddressesController.class).buildAndExpand("123").toString());
}
@Test
public void fromMethodWithPrefix() {
initWebApplicationContext(PathPrefixWebConfig.class);
this.request.setServerName("example.org");
this.request.setServerPort(9999);
this.request.setContextPath("/base");
assertEquals("http://example.org:9999/base/api/people/123/addresses/DE",
fromMethodCall(on(PersonsAddressesController.class).getAddressesForCountry("DE"))
.buildAndExpand("123").toString());
}
private void initWebApplicationContext(Class<?> configClass) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(configClass);
context.refresh();
this.request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, context);
}
static class Person {
@@ -555,6 +584,21 @@ public class MvcUriComponentsBuilderTests {
}
@EnableWebMvc
static class PathPrefixWebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", PersonsAddressesController.class::equals);
}
@Bean
public PersonsAddressesController controller() {
return new PersonsAddressesController();
}
}
@Controller
@RequestMapping("/hotels/{hotel}")
static class BookingControllerWithModelAndView {