Spring中 AntPathMatcher 类解析

474 阅读3分钟

Ant-style pattern syntax 语法说明

Ant-style pattern syntax(Ant 风格的模式语法)是一种用于匹配文件路径或 URL 的简单而强大的模式匹配语法。它最初源于 Apache Ant 构建工具,但现在被广泛应用于各种 Java 框架和工具中,特别是在 Spring Framework 中。

Ant 风格模式的主要特点包括:

  1. 精确匹配: 如果没有特殊字符,模式将精确匹配路径。 例如:/mypath/myfile.txt 只匹配完全相同的路径。
  2. ? 通配符: 匹配任何单个字符。 例如:/mypath/myfile?.txt 可以匹配 myfile1.txt, myfileA.txt 等。
  3. * 通配符: 匹配任意数量的字符(不包括目录分隔符 /)。 例如:/mypath/*.txt 匹配 /mypath/ 下的所有 .txt 文件。
  4. ** 通配符: 匹配任意数量的目录。 例如:/mypath/**/myfile.txt 可以匹配 /mypath/myfile.txt, /mypath/dir/myfile.txt, /mypath/dir/subdir/myfile.txt 等。
  5. 大括号 {} 表达式: 用于指定一组子模式。 例如:/mypath/{file1,file2}.txt 匹配 /mypath/file1.txt/mypath/file2.txt

使用示例:

  1. /app/*.x:匹配 /app 目录下所有以 .x 结尾的文件。
  2. /app/p?ttern:匹配 /app/pattern/app/pAttern,但不匹配 /app/pttern
  3. /**/example:匹配 /example, /foo/example, /foo/bar/example 等。
  4. /app/**/dir/file.:匹配 /app/dir/file., /app/foo/dir/file., /app/foo/bar/dir/file. 等。
  5. /app/{spring,summer}.jsp:匹配 /app/spring.jsp/app/summer.jsp

AntPathMatcher的应用

在 Spring Framework 中,Ant-style pattern syntax 的实现主要通过 AntPathMatcher 类来实现。这个类提供了 Ant 风格路径匹配的核心功能。让我们深入了解一下 Spring 是如何实现这个功能的:

  1. AntPathMatcher 类

AntPathMatcherorg.springframework.util.PathMatcher 的一个实现。主要方法包括:

  • boolean match(String pattern, String path):检查给定的路径是否匹配指定的模式。
  • boolean matchStart(String pattern, String path):检查路径是否匹配模式的开始部分。
  • String extractPathWithinPattern(String pattern, String path):从路径中提取与模式匹配的部分。
  1. 实现细节
  • 模式解析:AntPathMatcher 将模式字符串解析为一系列的 AntPathStringMatcher 对象,每个对象负责匹配路径的一个段。

  • 通配符处理:

    • ? 匹配单个字符
    • * 匹配零个或多个字符(不包括路径分隔符)
    • ** 匹配零个或多个目录
  • 缓存机制:为了提高性能,AntPathMatcher 使用内部缓存来存储已解析的模式。

  1. 在 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 进行匹配
    }
}
  1. 自定义和扩展

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