提交 de280b01 编写于 作者: R Rossen Stoyanchev

Support custom PathMatcher for MappedInterceptor's

Issue: SPR-11197
上级 abb8a93e
...@@ -18,6 +18,7 @@ package org.springframework.web.servlet.config; ...@@ -18,6 +18,7 @@ package org.springframework.web.servlet.config;
import java.util.List; import java.util.List;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
...@@ -44,6 +45,11 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser { ...@@ -44,6 +45,11 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
parserContext.pushContainingComponent(compDefinition); 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"); List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
for (Element interceptor : interceptors) { for (Element interceptor : interceptors) {
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class); RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
...@@ -66,6 +72,10 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser { ...@@ -66,6 +72,10 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser {
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns); mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean); mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);
if (pathMatcherRef != null) {
mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
}
String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef); String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName)); parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
} }
......
...@@ -16,12 +16,11 @@ ...@@ -16,12 +16,11 @@
package org.springframework.web.servlet.config.annotation; package org.springframework.web.servlet.config.annotation;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.handler.MappedInterceptor;
...@@ -40,6 +39,9 @@ public class InterceptorRegistration { ...@@ -40,6 +39,9 @@ public class InterceptorRegistration {
private final List<String> excludePatterns = new ArrayList<String>(); private final List<String> excludePatterns = new ArrayList<String>();
private PathMatcher pathMatcher;
/** /**
* Creates an {@link InterceptorRegistration} instance. * Creates an {@link InterceptorRegistration} instance.
*/ */
...@@ -64,6 +66,17 @@ public class InterceptorRegistration { ...@@ -64,6 +66,17 @@ public class InterceptorRegistration {
return this; 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 * Returns the underlying interceptor. If URL patterns are provided the returned type is
* {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}. * {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
...@@ -72,7 +85,12 @@ public class InterceptorRegistration { ...@@ -72,7 +85,12 @@ public class InterceptorRegistration {
if (this.includePatterns.isEmpty()) { if (this.includePatterns.isEmpty()) {
return this.interceptor; 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) { private static String[] toArray(List<String> list) {
......
...@@ -36,6 +36,8 @@ public final class MappedInterceptor { ...@@ -36,6 +36,8 @@ public final class MappedInterceptor {
private final HandlerInterceptor interceptor; private final HandlerInterceptor interceptor;
private PathMatcher pathMatcher;
/** /**
* Create a new MappedInterceptor instance. * Create a new MappedInterceptor instance.
...@@ -58,6 +60,7 @@ public final class MappedInterceptor { ...@@ -58,6 +60,7 @@ public final class MappedInterceptor {
this.interceptor = interceptor; this.interceptor = interceptor;
} }
/** /**
* Create a new MappedInterceptor instance. * Create a new MappedInterceptor instance.
* @param includePatterns the path patterns to map with a {@code null} value matching to all paths * @param includePatterns the path patterns to map with a {@code null} value matching to all paths
...@@ -77,6 +80,26 @@ public final class MappedInterceptor { ...@@ -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. * The path into the application the interceptor is mapped to.
*/ */
...@@ -97,9 +120,10 @@ public final class MappedInterceptor { ...@@ -97,9 +120,10 @@ public final class MappedInterceptor {
* @param pathMatcher a path matcher for path pattern matching * @param pathMatcher a path matcher for path pattern matching
*/ */
public boolean matches(String lookupPath, PathMatcher pathMatcher) { public boolean matches(String lookupPath, PathMatcher pathMatcher) {
PathMatcher pathMatcherToUse = (this.pathMatcher != null) ? this.pathMatcher : pathMatcher;
if (this.excludePatterns != null) { if (this.excludePatterns != null) {
for (String pattern : this.excludePatterns) { for (String pattern : this.excludePatterns) {
if (pathMatcher.match(pattern, lookupPath)) { if (pathMatcherToUse.match(pattern, lookupPath)) {
return false; return false;
} }
} }
...@@ -109,7 +133,7 @@ public final class MappedInterceptor { ...@@ -109,7 +133,7 @@ public final class MappedInterceptor {
} }
else { else {
for (String pattern : this.includePatterns) { for (String pattern : this.includePatterns) {
if (pathMatcher.match(pattern, lookupPath)) { if (pathMatcherToUse.match(pattern, lookupPath)) {
return true; return true;
} }
} }
......
...@@ -415,6 +415,20 @@ ...@@ -415,6 +415,20 @@
</xsd:complexType> </xsd:complexType>
</xsd:element> </xsd:element>
</xsd:choice> </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:complexType>
</xsd:element> </xsd:element>
......
...@@ -19,10 +19,7 @@ package org.springframework.web.servlet.config; ...@@ -19,10 +19,7 @@ package org.springframework.web.servlet.config;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
...@@ -47,6 +44,7 @@ import org.springframework.mock.web.test.MockRequestDispatcher; ...@@ -47,6 +44,7 @@ import org.springframework.mock.web.test.MockRequestDispatcher;
import org.springframework.mock.web.test.MockServletContext; import org.springframework.mock.web.test.MockServletContext;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.PathMatcher;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
...@@ -68,10 +66,7 @@ import org.springframework.web.method.support.InvocableHandlerMethod; ...@@ -68,10 +66,7 @@ import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.*;
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.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
...@@ -219,7 +214,7 @@ public class MvcNamespaceTests { ...@@ -219,7 +214,7 @@ public class MvcNamespaceTests {
@Test @Test
public void testInterceptors() throws Exception { public void testInterceptors() throws Exception {
loadBeanDefinitions("mvc-config-interceptors.xml", 18); loadBeanDefinitions("mvc-config-interceptors.xml", 20);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping); assertNotNull(mapping);
...@@ -231,11 +226,12 @@ public class MvcNamespaceTests { ...@@ -231,11 +226,12 @@ public class MvcNamespaceTests {
request.addParameter("theme", "green"); request.addParameter("theme", "green");
HandlerExecutionChain chain = mapping.getHandler(request); HandlerExecutionChain chain = mapping.getHandler(request);
assertEquals(4, chain.getInterceptors().length); assertEquals(5, chain.getInterceptors().length);
assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor); assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor); assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
assertTrue(chain.getInterceptors()[2] instanceof WebRequestHandlerInterceptorAdapter); assertTrue(chain.getInterceptors()[2] instanceof WebRequestHandlerInterceptorAdapter);
assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor); assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
assertTrue(chain.getInterceptors()[4] instanceof UserRoleAuthorizationInterceptor);
request.setRequestURI("/admin/users"); request.setRequestURI("/admin/users");
chain = mapping.getHandler(request); chain = mapping.getHandler(request);
...@@ -584,4 +580,42 @@ public class MvcNamespaceTests { ...@@ -584,4 +580,42 @@ public class MvcNamespaceTests {
public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { } 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 @@ ...@@ -16,10 +16,6 @@
package org.springframework.web.servlet.config.annotation; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -27,6 +23,8 @@ import java.util.List; ...@@ -27,6 +23,8 @@ import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
...@@ -40,6 +38,8 @@ import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapt ...@@ -40,6 +38,8 @@ import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapt
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor; import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
import static org.junit.Assert.*;
/** /**
* Test fixture with a {@link InterceptorRegistry}, two {@link HandlerInterceptor}s and two * Test fixture with a {@link InterceptorRegistry}, two {@link HandlerInterceptor}s and two
* {@link WebRequestInterceptor}s. * {@link WebRequestInterceptor}s.
...@@ -116,6 +116,15 @@ public class InterceptorRegistryTests { ...@@ -116,6 +116,15 @@ public class InterceptorRegistryTests {
verifyAdaptedInterceptor(interceptors.get(1), webRequestInterceptor2); 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 @Test
public void addWebRequestInterceptorsWithUrlPatterns() throws Exception { public void addWebRequestInterceptorsWithUrlPatterns() throws Exception {
registry.addWebRequestInterceptor(webRequestInterceptor1).addPathPatterns("/path1"); registry.addWebRequestInterceptor(webRequestInterceptor1).addPathPatterns("/path1");
......
...@@ -20,8 +20,12 @@ import static org.junit.Assert.*; ...@@ -20,8 +20,12 @@ import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.util.AntPathMatcher; import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import java.util.Comparator;
import java.util.Map;
/** /**
* Test fixture for {@link MappedInterceptor} tests. * Test fixture for {@link MappedInterceptor} tests.
* *
...@@ -75,4 +79,52 @@ public class MappedInterceptorTests { ...@@ -75,4 +79,52 @@ public class MappedInterceptorTests {
assertFalse(mappedInterceptor.matches("/admin/foo", pathMatcher)); 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 @@ ...@@ -26,4 +26,13 @@
<bean id="log4jInterceptor" <bean id="log4jInterceptor"
class="org.springframework.web.context.request.Log4jNestedDiagnosticContextInterceptor" /> 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> </beans>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册