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);
}
}