Support custom PathMatcher for MappedInterceptor's
Issue: SPR-11197
This commit is contained in:
parent
abb8a93e2f
commit
de280b01fe
|
|
@ -18,6 +18,7 @@ package org.springframework.web.servlet.config;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
|
|
@ -44,6 +45,11 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
|
|||
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
|
||||
parserContext.pushContainingComponent(compDefinition);
|
||||
|
||||
RuntimeBeanReference pathMatcherRef = null;
|
||||
if (element.hasAttribute("path-matcher")) {
|
||||
pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
|
||||
}
|
||||
|
||||
List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
|
||||
for (Element interceptor : interceptors) {
|
||||
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
|
||||
|
|
@ -66,6 +72,10 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
|
|||
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
|
||||
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);
|
||||
|
||||
if (pathMatcherRef != null) {
|
||||
mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
|
||||
}
|
||||
|
||||
String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,12 +16,11 @@
|
|||
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.handler.MappedInterceptor;
|
||||
|
||||
|
|
@ -40,6 +39,9 @@ public class InterceptorRegistration {
|
|||
|
||||
private final List<String> excludePatterns = new ArrayList<String>();
|
||||
|
||||
private PathMatcher pathMatcher;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an {@link InterceptorRegistration} instance.
|
||||
*/
|
||||
|
|
@ -64,6 +66,17 @@ public class InterceptorRegistration {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A PathMatcher implementation to use with this interceptor. This is an optional,
|
||||
* advanced property required only if using custom PathMatcher implementations
|
||||
* that support mapping metadata other than the Ant path patterns supported
|
||||
* by default.
|
||||
*/
|
||||
public InterceptorRegistration pathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying interceptor. If URL patterns are provided the returned type is
|
||||
* {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
|
||||
|
|
@ -72,7 +85,12 @@ public class InterceptorRegistration {
|
|||
if (this.includePatterns.isEmpty()) {
|
||||
return this.interceptor;
|
||||
}
|
||||
return new MappedInterceptor(toArray(this.includePatterns), toArray(this.excludePatterns), interceptor);
|
||||
MappedInterceptor mappedInterceptor = new MappedInterceptor(
|
||||
toArray(this.includePatterns), toArray(this.excludePatterns), interceptor);
|
||||
if (this.pathMatcher != null) {
|
||||
mappedInterceptor.setPathMatcher(this.pathMatcher);
|
||||
}
|
||||
return mappedInterceptor;
|
||||
}
|
||||
|
||||
private static String[] toArray(List<String> list) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ public final class MappedInterceptor {
|
|||
|
||||
private final HandlerInterceptor interceptor;
|
||||
|
||||
private PathMatcher pathMatcher;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MappedInterceptor instance.
|
||||
|
|
@ -58,6 +60,7 @@ public final class MappedInterceptor {
|
|||
this.interceptor = interceptor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new MappedInterceptor instance.
|
||||
* @param includePatterns the path patterns to map with a {@code null} value matching to all paths
|
||||
|
|
@ -77,6 +80,26 @@ public final class MappedInterceptor {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure a PathMatcher to use with this MappedInterceptor instead of the
|
||||
* one passed by default to the {@link #matches(String, org.springframework.util.PathMatcher)}
|
||||
* method. This is an advanced property that is only required when using custom
|
||||
* PathMatcher implementations that support mapping metadata other than the
|
||||
* Ant-style path patterns supported by default.
|
||||
*
|
||||
* @param pathMatcher the path matcher to use
|
||||
*/
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* The configured PathMatcher, or {@code null}.
|
||||
*/
|
||||
public PathMatcher getPathMatcher() {
|
||||
return this.pathMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path into the application the interceptor is mapped to.
|
||||
*/
|
||||
|
|
@ -97,9 +120,10 @@ public final class MappedInterceptor {
|
|||
* @param pathMatcher a path matcher for path pattern matching
|
||||
*/
|
||||
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
|
||||
PathMatcher pathMatcherToUse = (this.pathMatcher != null) ? this.pathMatcher : pathMatcher;
|
||||
if (this.excludePatterns != null) {
|
||||
for (String pattern : this.excludePatterns) {
|
||||
if (pathMatcher.match(pattern, lookupPath)) {
|
||||
if (pathMatcherToUse.match(pattern, lookupPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +133,7 @@ public final class MappedInterceptor {
|
|||
}
|
||||
else {
|
||||
for (String pattern : this.includePatterns) {
|
||||
if (pathMatcher.match(pattern, lookupPath)) {
|
||||
if (pathMatcherToUse.match(pattern, lookupPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -415,6 +415,20 @@
|
|||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="path-matcher" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation source="java:org.springframework.util.PathMatcher"><![CDATA[
|
||||
The bean name of a PathMatcher implementation to use with nested interceptors. This is an optional,
|
||||
advanced property required only if using custom PathMatcher implementations that support mapping
|
||||
metadata other than the Ant path patterns supported by default.
|
||||
]]></xsd:documentation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="java:org.springframework.util.PathMatcher" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ package org.springframework.web.servlet.config;
|
|||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.RequestDispatcher;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
|
@ -47,6 +44,7 @@ import org.springframework.mock.web.test.MockRequestDispatcher;
|
|||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
|
@ -68,10 +66,7 @@ import org.springframework.web.method.support.InvocableHandlerMethod;
|
|||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
|
||||
import org.springframework.web.servlet.handler.*;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
|
||||
|
|
@ -219,7 +214,7 @@ public class MvcNamespaceTests {
|
|||
|
||||
@Test
|
||||
public void testInterceptors() throws Exception {
|
||||
loadBeanDefinitions("mvc-config-interceptors.xml", 18);
|
||||
loadBeanDefinitions("mvc-config-interceptors.xml", 20);
|
||||
|
||||
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
|
||||
assertNotNull(mapping);
|
||||
|
|
@ -231,11 +226,12 @@ public class MvcNamespaceTests {
|
|||
request.addParameter("theme", "green");
|
||||
|
||||
HandlerExecutionChain chain = mapping.getHandler(request);
|
||||
assertEquals(4, chain.getInterceptors().length);
|
||||
assertEquals(5, chain.getInterceptors().length);
|
||||
assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
|
||||
assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
|
||||
assertTrue(chain.getInterceptors()[2] instanceof WebRequestHandlerInterceptorAdapter);
|
||||
assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
|
||||
assertTrue(chain.getInterceptors()[4] instanceof UserRoleAuthorizationInterceptor);
|
||||
|
||||
request.setRequestURI("/admin/users");
|
||||
chain = mapping.getHandler(request);
|
||||
|
|
@ -584,4 +580,42 @@ public class MvcNamespaceTests {
|
|||
|
||||
public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { }
|
||||
|
||||
public static class TestPathMatcher implements PathMatcher {
|
||||
|
||||
@Override
|
||||
public boolean isPattern(String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String pattern, String path) {
|
||||
return path.matches(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchStart(String pattern, String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractPathWithinPattern(String pattern, String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<String> getPatternComparator(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String combine(String pattern1, String pattern2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@
|
|||
|
||||
package org.springframework.web.servlet.config.annotation;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
@ -27,6 +23,8 @@ import java.util.List;
|
|||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.mock.web.test.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.test.MockHttpServletResponse;
|
||||
import org.springframework.ui.ModelMap;
|
||||
|
|
@ -40,6 +38,8 @@ import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapt
|
|||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Test fixture with a {@link InterceptorRegistry}, two {@link HandlerInterceptor}s and two
|
||||
* {@link WebRequestInterceptor}s.
|
||||
|
|
@ -116,6 +116,15 @@ public class InterceptorRegistryTests {
|
|||
verifyAdaptedInterceptor(interceptors.get(1), webRequestInterceptor2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addInterceptorsWithCustomPathMatcher() {
|
||||
PathMatcher pathMatcher = Mockito.mock(PathMatcher.class);
|
||||
registry.addInterceptor(interceptor1).addPathPatterns("/path1/**").pathMatcher(pathMatcher);
|
||||
|
||||
MappedInterceptor mappedInterceptor = (MappedInterceptor) registry.getInterceptors().get(0);
|
||||
assertSame(pathMatcher, mappedInterceptor.getPathMatcher());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addWebRequestInterceptorsWithUrlPatterns() throws Exception {
|
||||
registry.addWebRequestInterceptor(webRequestInterceptor1).addPathPatterns("/path1");
|
||||
|
|
|
|||
|
|
@ -20,8 +20,12 @@ import static org.junit.Assert.*;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link MappedInterceptor} tests.
|
||||
*
|
||||
|
|
@ -75,4 +79,52 @@ public class MappedInterceptorTests {
|
|||
assertFalse(mappedInterceptor.matches("/admin/foo", pathMatcher));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customPathMatcher() {
|
||||
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo/[0-9]*" }, this.interceptor);
|
||||
mappedInterceptor.setPathMatcher(new TestPathMatcher());
|
||||
|
||||
assertTrue(mappedInterceptor.matches("/foo/123", pathMatcher));
|
||||
assertFalse(mappedInterceptor.matches("/foo/bar", pathMatcher));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class TestPathMatcher implements PathMatcher {
|
||||
|
||||
@Override
|
||||
public boolean isPattern(String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(String pattern, String path) {
|
||||
return path.matches(pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchStart(String pattern, String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String extractPathWithinPattern(String pattern, String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comparator<String> getPatternComparator(String path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String combine(String pattern1, String pattern2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,13 @@
|
|||
<bean id="log4jInterceptor"
|
||||
class="org.springframework.web.context.request.Log4jNestedDiagnosticContextInterceptor" />
|
||||
|
||||
<mvc:interceptors path-matcher="pathMatcher">
|
||||
<mvc:interceptor>
|
||||
<mvc:mapping path="/accounts/[0-9]*" />
|
||||
<bean class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor" />
|
||||
</mvc:interceptor>
|
||||
</mvc:interceptors>
|
||||
|
||||
<bean id="pathMatcher" class="org.springframework.web.servlet.config.MvcNamespaceTests$TestPathMatcher" />
|
||||
|
||||
</beans>
|
||||
|
|
|
|||
Loading…
Reference in New Issue