Ant-style pattern syntax 语法说明
Ant-style pattern syntax(Ant 风格的模式语法)是一种用于匹配文件路径或 URL 的简单而强大的模式匹配语法。它最初源于 Apache Ant 构建工具,但现在被广泛应用于各种 Java 框架和工具中,特别是在 Spring Framework 中。
Ant 风格模式的主要特点包括:
- 精确匹配: 如果没有特殊字符,模式将精确匹配路径。 例如:
/mypath/myfile.txt
只匹配完全相同的路径。 ?
通配符: 匹配任何单个字符。 例如:/mypath/myfile?.txt
可以匹配myfile1.txt
,myfileA.txt
等。*
通配符: 匹配任意数量的字符(不包括目录分隔符/
)。 例如:/mypath/*.txt
匹配/mypath/
下的所有.txt
文件。**
通配符: 匹配任意数量的目录。 例如:/mypath/**/myfile.txt
可以匹配/mypath/myfile.txt
,/mypath/dir/myfile.txt
,/mypath/dir/subdir/myfile.txt
等。- 大括号
{}
表达式: 用于指定一组子模式。 例如:/mypath/{file1,file2}.txt
匹配/mypath/file1.txt
和/mypath/file2.txt
。
使用示例:
/app/*.x
:匹配/app
目录下所有以.x
结尾的文件。/app/p?ttern
:匹配/app/pattern
和/app/pAttern
,但不匹配/app/pttern
。/**/example
:匹配/example
,/foo/example
,/foo/bar/example
等。/app/**/dir/file.
:匹配/app/dir/file.
,/app/foo/dir/file.
,/app/foo/bar/dir/file.
等。/app/{spring,summer}.jsp
:匹配/app/spring.jsp
和/app/summer.jsp
。
AntPathMatcher的应用
在 Spring Framework 中,Ant-style pattern syntax 的实现主要通过 AntPathMatcher
类来实现。这个类提供了 Ant 风格路径匹配的核心功能。让我们深入了解一下 Spring 是如何实现这个功能的:
- AntPathMatcher 类
AntPathMatcher
是 org.springframework.util.PathMatcher
的一个实现。主要方法包括:
boolean match(String pattern, String path)
:检查给定的路径是否匹配指定的模式。boolean matchStart(String pattern, String path)
:检查路径是否匹配模式的开始部分。String extractPathWithinPattern(String pattern, String path)
:从路径中提取与模式匹配的部分。
- 实现细节
-
模式解析:
AntPathMatcher
将模式字符串解析为一系列的AntPathStringMatcher
对象,每个对象负责匹配路径的一个段。 -
通配符处理:
?
匹配单个字符*
匹配零个或多个字符(不包括路径分隔符)**
匹配零个或多个目录
-
缓存机制:为了提高性能,
AntPathMatcher
使用内部缓存来存储已解析的模式。
- 在 Spring 中的应用
a. URL 映射: 在 RequestMappingHandlerMapping
中使用 AntPathMatcher
来匹配 URL 模式。
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping {
// ...
@Override
protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}
// ...
}
b. 资源加载: 在 ResourcePatternResolver
中使用 AntPathMatcher
来解析资源路径。
public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
private PathMatcher pathMatcher = new AntPathMatcher();
// ...
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException {
// 使用 pathMatcher 进行匹配
}
}
c. 组件扫描: 在 ClassPathScanningCandidateComponentProvider
中使用 AntPathMatcher
来匹配类路径。
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {
private PathMatcher pathMatcher = new AntPathMatcher();
// ...
protected boolean matchesFilter(String metadataReaderFactory, MetadataReader metadataReader) {
// 使用 pathMatcher 进行匹配
}
}
- 自定义和扩展
Spring 允许通过配置自定义 PathMatcher
实现:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
AntPathMatcher matcher = new AntPathMatcher();
matcher.setCaseSensitive(false);
configurer.setPathMatcher(matcher);
}
}
AntPathMatcher 使用方式解析
我们可以使用测试用例来学习这个类的使用方式,测试用例如下
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.util.AntPathMatcher;
public class AntPathMatcherTest {
@Test
void testIsPattern() {
AntPathMatcher matcher = new AntPathMatcher();
assert matcher.isPattern("test/*");
assert matcher.isPattern("test/**");
assert matcher.isPattern("test/?");
assert matcher.isPattern("test/{name}");
assert matcher.isPattern("test/{name:[a-z]+}");
Assertions.assertFalse(matcher.isPattern("/user/profile"));
}
@Test
void testMatch() {
AntPathMatcher matcher = new AntPathMatcher();
boolean matches = matcher.match("/user/*.html", "/user/profile.html");
Assertions.assertTrue(matches);
boolean notMatches = matcher.match("/user/*.html", "/admin/profile.html");
Assertions.assertFalse(notMatches);
}
@Test
void testMatchStart() {
AntPathMatcher matcher = new AntPathMatcher();
boolean matches = matcher.matchStart("/user/**", "/user/profile/edit"); // 返回 true
Assertions.assertTrue(matches);
boolean notMatches = matcher.matchStart("/user/*", "/user/profile/edit"); // 返回 false
Assertions.assertFalse(notMatches);
}
@Test
void testExtractPathWithinPattern() {
AntPathMatcher matcher = new AntPathMatcher();
String extracted = matcher.extractPathWithinPattern("/user/**", "/user/profile/edit");
Assertions.assertEquals("profile/edit", extracted);
}
@Test
void testExtractUriTemplateVariables() {
AntPathMatcher matcher = new AntPathMatcher();
Map<String, String> variables = matcher.extractUriTemplateVariables("/user/{id}", "/user/123");
Assertions.assertEquals("123", variables.get("id"));
}
@Test
void testCombine() {
AntPathMatcher matcher = new AntPathMatcher();
String combined = matcher.combine("/user/*", "profile");
Assertions.assertEquals("/user/profile", combined);
}
}