提交 19dc9816 编写于 作者: R Rossen Stoyanchev

Path prefixes for groups of controllers

Issue: SPR-16336
上级 31159a85
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-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.
......@@ -16,7 +16,11 @@
package org.springframework.web.reactive.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerTypePredicate;
/**
* Assist with configuring {@code HandlerMapping}'s with path matching options.
......@@ -34,6 +38,9 @@ public class PathMatchConfigurer {
@Nullable
private Boolean caseSensitiveMatch;
@Nullable
private Map<String, HandlerTypePredicate> pathPrefixes;
/**
* Whether to match to URLs irrespective of their case.
......@@ -55,6 +62,22 @@ public class PathMatchConfigurer {
return this;
}
/**
* Configure a path prefix to apply to matching controller methods.
* <p>Prefixes are used to enrich the mappings of every {@code @RequestMapping}
* method whose controller type is matched by the corresponding
* {@link HandlerTypePredicate}. The prefix for the first matching predicate
* is used.
* @param prefix the path prefix to apply
* @param predicate a predicate for matching controller types
* @since 5.1
*/
public PathMatchConfigurer addPathPrefix(String prefix, HandlerTypePredicate predicate) {
this.pathPrefixes = this.pathPrefixes == null ? new LinkedHashMap<>() : this.pathPrefixes;
this.pathPrefixes.put(prefix, predicate);
return this;
}
@Nullable
protected Boolean isUseTrailingSlashMatch() {
return this.trailingSlashMatch;
......@@ -65,4 +88,8 @@ public class PathMatchConfigurer {
return this.caseSensitiveMatch;
}
@Nullable
protected Map<String, HandlerTypePredicate> getPathPrefixes() {
return this.pathPrefixes;
}
}
......@@ -44,6 +44,7 @@ import org.springframework.validation.Validator;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
......@@ -119,14 +120,22 @@ public class WebFluxConfigurationSupport implements ApplicationContextAware {
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
Boolean useCaseSensitiveMatch = configurer.isUseCaseSensitiveMatch();
if (useCaseSensitiveMatch != null) {
mapping.setUseCaseSensitiveMatch(useCaseSensitiveMatch);
}
Map<String, HandlerTypePredicate> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
......
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-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.
......@@ -18,6 +18,9 @@ package org.springframework.web.reactive.result.method.annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
......@@ -25,11 +28,13 @@ 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.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
......@@ -48,6 +53,8 @@ import org.springframework.web.reactive.result.method.RequestMappingInfoHandlerM
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
implements EmbeddedValueResolverAware {
private final Map<String, HandlerTypePredicate> pathPrefixes = new LinkedHashMap<>();
private RequestedContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder().build();
@Nullable
......@@ -56,6 +63,22 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
/**
* Configure path prefixes to apply to controller methods.
* <p>Prefixes are used to enrich the mappings of every {@code @RequestMapping}
* method whose controller type is matched by the corresponding
* {@link HandlerTypePredicate} in the map. The prefix for the first matching
* predicate is used, assuming the input map has predictable order.
* @param prefixes a map with path prefixes as key
* @since 5.1
*/
public void setPathPrefixes(Map<String, HandlerTypePredicate> prefixes) {
this.pathPrefixes.clear();
prefixes.entrySet().stream()
.filter(entry -> StringUtils.hasText(entry.getKey()))
.forEach(entry -> this.pathPrefixes.put(entry.getKey(), entry.getValue()));
}
/**
* Set the {@link RequestedContentTypeResolver} to use to determine requested media types.
* If not set, the default constructor is used.
......@@ -80,6 +103,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
/**
* The configured path prefixes as a read-only, possibly empty map.
* @since 5.1
*/
public Map<String, HandlerTypePredicate> getPathPrefixes() {
return Collections.unmodifiableMap(this.pathPrefixes);
}
/**
* Return the configured {@link RequestedContentTypeResolver}.
*/
......@@ -113,6 +144,16 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
if (typeInfo != null) {
info = typeInfo.combine(info);
}
for (Map.Entry<String, HandlerTypePredicate> 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;
}
}
}
return info;
}
......
......@@ -18,8 +18,10 @@ package org.springframework.web.reactive.config;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.security.Principal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Test;
......@@ -47,11 +49,17 @@ import org.springframework.util.MimeTypeUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.handler.AbstractUrlHandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
......@@ -67,6 +75,7 @@ import org.springframework.web.server.WebHandler;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.ResolvableType.forClassWithGenerics;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
......@@ -110,7 +119,7 @@ public class WebFluxConfigurationSupportTests {
}
@Test
public void customPathMatchConfig() throws Exception {
public void customPathMatchConfig() {
ApplicationContext context = loadConfig(CustomPatchMatchConfig.class);
final Field field = ReflectionUtils.findField(PathPatternParser.class, "matchOptionalTrailingSeparator");
ReflectionUtils.makeAccessible(field);
......@@ -123,6 +132,11 @@ public class WebFluxConfigurationSupportTests {
assertNotNull(patternParser);
boolean matchOptionalTrailingSlash = (boolean) ReflectionUtils.getField(field, patternParser);
assertFalse(matchOptionalTrailingSlash);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
assertEquals(1, map.size());
assertEquals(Collections.singleton(new PathPatternParser().parse("/api/user/{id}")),
map.keySet().iterator().next().getPatternsCondition().getPatterns());
}
@Test
......@@ -295,6 +309,23 @@ public class WebFluxConfigurationSupportTests {
@Override
public void configurePathMatching(PathMatchConfigurer configurer) {
configurer.setUseTrailingSlashMatch(false);
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
@Bean
UserController userController() {
return new UserController();
}
}
@RestController
@RequestMapping("/user")
static class UserController {
@GetMapping("/{id}")
public Principal getUser() {
return mock(Principal.class);
}
}
......
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-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.
......@@ -21,7 +21,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import org.junit.Before;
......@@ -37,13 +39,17 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.reactive.result.method.RequestMappingInfo;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.*;
/**
* Unit tests for {@link RequestMappingHandlerMapping}.
......@@ -64,9 +70,7 @@ public class RequestMappingHandlerMappingTests {
@Test
public void resolveEmbeddedValuesInPatterns() {
this.handlerMapping.setEmbeddedValueResolver(
value -> "/${pattern}/bar".equals(value) ? "/foo/bar" : value
);
this.handlerMapping.setEmbeddedValueResolver(value -> "/${pattern}/bar".equals(value) ? "/foo/bar" : value);
String[] patterns = new String[] { "/foo", "/${pattern}/bar" };
String[] result = this.handlerMapping.resolveEmbeddedValuesInPatterns(patterns);
......@@ -74,6 +78,20 @@ public class RequestMappingHandlerMappingTests {
assertArrayEquals(new String[] { "/foo", "/foo/bar" }, result);
}
@Test
public void pathPrefix() throws NoSuchMethodException {
this.handlerMapping.setEmbeddedValueResolver(value -> "/${prefix}".equals(value) ? "/api" : value);
this.handlerMapping.setPathPrefixes(Collections.singletonMap(
"/${prefix}", HandlerTypePredicate.forAnnotation(RestController.class)));
Method method = UserController.class.getMethod("getUser");
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, UserController.class);
assertNotNull(info);
assertEquals(Collections.singleton(new PathPatternParser().parse("/api/user/{id}")),
info.getPatternsCondition().getPatterns());
}
@Test
public void resolveRequestMappingViaComposedAnnotation() throws Exception {
RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST);
......@@ -191,4 +209,15 @@ public class RequestMappingHandlerMappingTests {
String[] value() default {};
}
@RestController
@RequestMapping("/user")
static class UserController {
@GetMapping("/{id}")
public Principal getUser() {
return mock(Principal.class);
}
}
}
......@@ -16,8 +16,12 @@
package org.springframework.web.servlet.config.annotation;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.util.UrlPathHelper;
/**
......@@ -53,6 +57,9 @@ public class PathMatchConfigurer {
@Nullable
private PathMatcher pathMatcher;
@Nullable
private Map<String, HandlerTypePredicate> pathPrefixes;
/**
* Whether to use suffix pattern match (".*") when matching patterns to
......@@ -110,6 +117,22 @@ public class PathMatchConfigurer {
return this;
}
/**
* Configure a path prefix to apply to matching controller methods.
* <p>Prefixes are used to enrich the mappings of every {@code @RequestMapping}
* method whose controller type is matched by the corresponding
* {@link HandlerTypePredicate}. The prefix for the first matching predicate
* is used.
* @param prefix the prefix to apply
* @param predicate a predicate for matching controller types
* @since 5.1
*/
public PathMatchConfigurer addPathPrefix(String prefix, HandlerTypePredicate predicate) {
this.pathPrefixes = this.pathPrefixes == null ? new LinkedHashMap<>() : this.pathPrefixes;
this.pathPrefixes.put(prefix, predicate);
return this;
}
@Nullable
public Boolean isUseSuffixPatternMatch() {
......@@ -136,4 +159,8 @@ public class PathMatchConfigurer {
return this.pathMatcher;
}
@Nullable
protected Map<String, HandlerTypePredicate> getPathPrefixes() {
return this.pathPrefixes;
}
}
......@@ -70,6 +70,7 @@ import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
......@@ -284,15 +285,18 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
Boolean useSuffixPatternMatch = configurer.isUseSuffixPatternMatch();
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useSuffixPatternMatch != null) {
mapping.setUseSuffixPatternMatch(useSuffixPatternMatch);
}
Boolean useRegisteredSuffixPatternMatch = configurer.isUseRegisteredSuffixPatternMatch();
if (useRegisteredSuffixPatternMatch != null) {
mapping.setUseRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch);
}
Boolean useTrailingSlashMatch = configurer.isUseTrailingSlashMatch();
if (useTrailingSlashMatch != null) {
mapping.setUseTrailingSlashMatch(useTrailingSlashMatch);
}
......@@ -307,6 +311,11 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
mapping.setPathMatcher(pathMatcher);
}
Map<String, HandlerTypePredicate> pathPrefixes = configurer.getPathPrefixes();
if (pathPrefixes != null) {
mapping.setPathPrefixes(pathPrefixes);
}
return mapping;
}
......
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-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.
......@@ -18,7 +18,10 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
......@@ -28,12 +31,14 @@ 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;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
import org.springframework.web.servlet.handler.RequestMatchResult;
......@@ -62,6 +67,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
private boolean useTrailingSlashMatch = true;
private final Map<String, HandlerTypePredicate> pathPrefixes = new LinkedHashMap<>();
private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
@Nullable
......@@ -102,6 +109,22 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
this.useTrailingSlashMatch = useTrailingSlashMatch;
}
/**
* Configure path prefixes to apply to controller methods.
* <p>Prefixes are used to enrich the mappings of every {@code @RequestMapping}
* method whose controller type is matched by the corresponding
* {@link HandlerTypePredicate} in the map. The prefix for the first matching
* predicate is used, assuming the input map has predictable order.
* @param prefixes a map with path prefixes as key
* @since 5.1
*/
public void setPathPrefixes(Map<String, HandlerTypePredicate> prefixes) {
this.pathPrefixes.clear();
prefixes.entrySet().stream()
.filter(entry -> StringUtils.hasText(entry.getKey()))
.forEach(entry -> this.pathPrefixes.put(entry.getKey(), entry.getValue()));
}
/**
* Set the {@link ContentNegotiationManager} to use to determine requested media types.
* If not set, the default constructor is used.
......@@ -151,6 +174,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
return this.useTrailingSlashMatch;
}
/**
* The configured path prefixes as a read-only, possibly empty map.
* @since 5.1
*/
public Map<String, HandlerTypePredicate> getPathPrefixes() {
return Collections.unmodifiableMap(this.pathPrefixes);
}
/**
* Return the configured {@link ContentNegotiationManager}.
*/
......@@ -195,6 +226,16 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
if (typeInfo != null) {
info = typeInfo.combine(info);
}
for (Map.Entry<String, HandlerTypePredicate> 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;
}
}
}
return info;
}
......
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-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.
......@@ -16,6 +16,7 @@
package org.springframework.web.servlet.config.annotation;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
......@@ -48,7 +49,9 @@ import org.springframework.validation.Errors;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
......@@ -56,6 +59,8 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
......@@ -70,6 +75,7 @@ import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
......@@ -86,6 +92,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.*;
import static org.springframework.http.MediaType.APPLICATION_ATOM_XML;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.http.MediaType.APPLICATION_XML;
......@@ -114,6 +121,7 @@ public class WebMvcConfigurationSupportExtensionTests {
this.context = new StaticWebApplicationContext();
this.context.setServletContext(new MockServletContext(new FileSystemResourceLoader()));
this.context.registerSingleton("controller", TestController.class);
this.context.registerSingleton("userController", UserController.class);
this.config = new TestWebMvcConfigurationSupport();
this.config.setApplicationContext(this.context);
......@@ -135,6 +143,15 @@ public class WebMvcConfigurationSupportExtensionTests {
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass());
assertEquals(ResourceUrlProviderExposingInterceptor.class, chain.getInterceptors()[2].getClass());
Map<RequestMappingInfo, HandlerMethod> map = rmHandlerMapping.getHandlerMethods();
assertEquals(2, map.size());
RequestMappingInfo info = map.entrySet().stream()
.filter(entry -> entry.getValue().getBeanType().equals(UserController.class))
.findFirst()
.orElseThrow(() -> new AssertionError("UserController bean not found"))
.getKey();
assertEquals(Collections.singleton("/api/user/{id}"), info.getPatternsCondition().getPatterns());
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping) this.config.viewControllerHandlerMapping();
handlerMapping.setApplicationContext(this.context);
assertNotNull(handlerMapping);
......@@ -402,6 +419,7 @@ public class WebMvcConfigurationSupportExtensionTests {
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPathMatcher(new TestPathMatcher());
configurer.setUrlPathHelper(new TestPathHelper());
configurer.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
@Override
......@@ -453,4 +471,16 @@ public class WebMvcConfigurationSupportExtensionTests {
private class TestPathHelper extends UrlPathHelper {}
private class TestPathMatcher extends AntPathMatcher {}
@RestController
@RequestMapping("/user")
static class UserController {
@GetMapping("/{id}")
public Principal getUser() {
return mock(Principal.class);
}
}
}
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-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.
......@@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
......@@ -42,14 +43,13 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Tests for {@link RequestMappingHandlerMapping}.
......@@ -140,6 +140,19 @@ public class RequestMappingHandlerMappingTests {
assertArrayEquals(new String[] { "/foo", "/foo/bar" }, result);
}
@Test
public void pathPrefix() throws NoSuchMethodException {
this.handlerMapping.setEmbeddedValueResolver(value -> "/${prefix}".equals(value) ? "/api" : value);
this.handlerMapping.setPathPrefixes(Collections.singletonMap(
"/${prefix}", HandlerTypePredicate.forAnnotation(RestController.class)));
Method method = UserController.class.getMethod("getUser");
RequestMappingInfo info = this.handlerMapping.getMappingForMethod(method, UserController.class);
assertNotNull(info);
assertEquals(Collections.singleton("/api/user/{id}"), info.getPatternsCondition().getPatterns());
}
@Test
public void resolveRequestMappingViaComposedAnnotation() throws Exception {
RequestMappingInfo info = assertComposedAnnotationMapping("postJson", "/postJson", RequestMethod.POST);
......@@ -245,6 +258,7 @@ public class RequestMappingHandlerMappingTests {
}
@RequestMapping(method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
......@@ -256,4 +270,15 @@ public class RequestMappingHandlerMappingTests {
String[] value() default {};
}
@RestController
@RequestMapping("/user")
static class UserController {
@GetMapping("/{id}")
public Principal getUser() {
return mock(Principal.class);
}
}
}
......@@ -2929,16 +2929,8 @@ match to incoming URLs without versions -- e.g. `"/jquery/jquery.min.js"` to
=== Path Matching
[.small]#<<web.adoc#mvc-config-path-matching,Same in Spring MVC>>#
Spring WebFlux uses parsed representation of path patterns -- i.e. `PathPattern`, and also
the incoming request path -- i.e. `RequestPath`, which eliminates the need to indicate
whether to decode the request path, or remove semicolon content, since `PathPattern`
can now access decoded path segment values and match safely.
Spring WebFlux also does not support suffix pattern matching so effectively there are only two
minor options to customize related to path matching -- whether to match trailing slashes
(`true` by default) and whether the match is case-sensitive (`false`).
To customize those options:
Customize options related to path matching. For details on the individual options, see the
{api-spring-framework}/web/reactive/config/PathMatchConfigurer.html[PathMatchConfigurer] Javadoc.
[source,java,indent=0]
[subs="verbatim,quotes"]
......@@ -2949,12 +2941,29 @@ To customize those options:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
// ...
configurer
.setUseCaseSensitiveMatch(true)
.setUseTrailingSlashMatch(false)
.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
}
----
[TIP]
====
Spring WebFlux relies on a parsed representation of the request path called
`RequestPath` for access to decoded path segment values, with semicolon content removed
(i.e. path/matrix variables). That means, unlike Spring MVC, there is no need to indicate
neither whether to decode the request path, nor whether to remove semicolon content for
path matching purposes.
Spring WebFlux also does not support suffix pattern matching, unlike Spring MVC, where we
also <<web.adoc#mvc-ann-requestmapping-suffix-pattern-match,recommend>> moving away from
reliance on it.
====
[[webflux-config-advanced-java]]
......
......@@ -4644,9 +4644,9 @@ Or in XML:
=== Path Matching
[.small]#<<web-reactive.adoc#webflux-config-path-matching,Same in Spring WebFlux>>#
This allows customizing options related to URL matching and treatment of the URL.
For details on the individual options check out the
{api-spring-framework}/web/servlet/config/annotation/PathMatchConfigurer.html[PathMatchConfigurer] API.
Customize options related to path matching, and treatment of the URL.
For details on the individual options, see the
{api-spring-framework}/web/servlet/config/annotation/PathMatchConfigurer.html[PathMatchConfigurer] Javadoc.
Example in Java config:
......@@ -4664,7 +4664,9 @@ Example in Java config:
.setUseTrailingSlashMatch(false)
.setUseRegisteredSuffixPatternMatch(true)
.setPathMatcher(antPathMatcher())
.setUrlPathHelper(urlPathHelper());
.setUrlPathHelper(urlPathHelper())
.addPathPrefix("/api",
HandlerTypePredicate.forAnnotation(RestController.class));
}
@Bean
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册