AntPathMatcher路径匹配器

AntPathMatcher路径匹配器

前言

  • @RequestMapping的URL是支持Ant风格的

  • @ComponentScan的扫描包路径是支持Ant风格的

  • @PropertySource导入资源是支持Ant分隔的(如:classpath:app-*.properties)

  • 在描述路径时有个常见叫法:Ant风格的URL。那么到底什么是Ant风格?关于这个概念,我特地的谷歌一下、百度一下、bing一下,无一所获(没有一个确切的定义),难道这个盛行的概念真的只能意会吗?

    直到我在Spring中AntPathMatcher的描述中看到一句话:这是从Apache Ant借用的一个概念。 “年轻”的朋友可能从没用过甚至没听过Ant,它是一个构建工具,在2010年之前发挥着大作用,但之后逐渐被Maven/Gradle取代,现已几乎销声匿迹。虽然Ant“已死”,但Ant风格似乎要千古。借助Spring强大的号召力,该概念似乎已是规范一样的存在,大家在不成文的约定着、交流着、书写着。

    那么,既然Ant风格贯穿于开发的方方面面,怀着一知半解的态度使用着实为不好。今天咱们就深入聊聊,进行全方位的讲解。

    版本约定
    JDK:8
    Spring Framework:5.3.x
    正文
    在Spring 5之前,Spring技术栈体系内几乎所有的Ant风格均由AntPathMatcher提供支持。

    PathMatcher路径匹配器
    PathMatcher是抽象接口,该接口抽象出了路径匹配器的概念,用于对path路径进行匹配。它提供如下方法:

    细节:PathMatcher所在的包为org.springframework.util.PathMatcher,属于spring-core核心模块,表示它可运用在任意模块,not only for web。

    一个路径匹配器提供这些方法在情理之中,这些方法见名知意理解起来也不难,下面稍作解释:

    boolean isPattern(String path):判断path是否是一个模式字符串(一般含有指定风格的特殊通配符就算是模式了)
    boolean match(String pattern, String path):最重要的方法。判断path和模式pattern是否匹配(注意:二者都是字符串,传值不要传反了哈)
    boolean matchStart(String pattern, String path):判断path是否和模式pattern前缀匹配(前缀匹配:path的前缀匹配上patter了即可,当然全部匹配也是可以的)
    String extractPathWithinPattern(String pattern, String path):返回和pattern模式真正匹配上的那部分字符串。举例:/api/yourbatman/*.html为pattern,/api/yourbatman/form.html为path,那么该方法返回结果为form.html(注意:返回结果永远不为null,可能是空串)
    Map<String, String> extractUriTemplateVariables(String pattern, String path):提取path中模板变量。举例:/api/yourbatman/{age}为pattern,/api/yourbatman/18为path,那么该方法返回结果为Map值为{“age” : 18}
    Comparator getPatternComparator(String path):路径比较器,用于排序确定优先级高低
    String combine(String pattern1, String pattern2):合并两个pattern模式,组合算法由具体实现自由决定
    该接口规定了作为路径匹配器一些必要的方法,同时也开放了一些行为策略如getPatternComparator、combine等由实现类自行决定。或许你还觉得这里某些方法有点抽象,那么下面就开始实战。

    正则表达式 vs Ant风格则表达式(regular expression):描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。

    正则表达式是由普通字符(例如字符a到z)以及特殊字符(又称"元字符")组成的文字模式。重点是它的元字符,举例说明:

    ?:匹配前面的子表达式零次或一次
    *:匹配前面的子表达式零次或多次
    +:匹配前面的子表达式一次或多次
    .:匹配除换行符 \n 之外的任何单字符

    正则表达式几乎所有编程语言都支持的通用模式,具有普适性(适用于任意字符串的匹配)、功能非常强大等特点。除此之外正则表达式还有“重”、“难”等特点,具有一定上手门槛、高并发情况下执行效率低也都是它摆脱不了的“特性”。Ant风格(Ant Style):该风格源自Apache的Ant项目,若你是个“老”程序员或许你还用过Apache Ant,若你是个小鲜肉也许闻所未闻,毕竟现在是Maven(Gradle)的天下。

    Ant风格简单的讲,它是一种精简的匹配模式,仅用于匹配路径or目录。使用大家熟悉的(这点很关键)的通配符:

通配符说明
*匹配任意数量的字符
**匹配任意层级的路径/目录
?匹配任意字符

看到没,这才比较符合咱们的习惯:*代表任意通配符才是正解嘛,而不是像正则一样代表匹配的数量来得让人“费解”。

**直接用于目录级别的匹配,可谓对URL这种字符串非常友好

最佳实践场景
正则表达式具有功能非常强大的特性,从理论上来讲,它可以用于任何场景,但是有些场景它并非最佳实践。

举个例子:在自定义的登录过滤器中,经常会放行一些API接口让免登录即可访问,这是典型的URL白名单场景,这个时候就会涉及到URL的匹配方式问题,一般会有如下方案:

精确匹配:url.equals(“/api/v1/yourbatman/adress”)。缺点:硬编码式一个个罗列,易造成错误且不好维护
前缀匹配:url.startsWith(“/api/v1/yourbatman”)。这也算一种匹配模式,可以批量处理某一类URL。缺点是:匹配范围过大易造成误伤,或者范围过小无法形成有效匹配,总之就是欠缺灵活度
包含匹配:url.contains(“/yourbatman”)。这个缺点比较明显:强依赖于URL的书写规范(如白名单的URL都必须包含指定子串),并且极易造成误伤
正则表达式匹配:Pattern.compile(“正则表达式”)…matcher(url).find()。它的最大优点是可以满足几乎任意的URL(包括精确、模式等),但最大的缺点是书写比较复杂,用时多少这和coder的水平强相关,另外这对后期维护也带来了一定挑战~
经常会听到这样一句话:“通过正则表达式或者Ant风格的路径表达式来做URL匹配”。正所谓“杀鸡何必用牛刀”,URL相较于普通的字符串具有很强的规律性:标准的分段式。因此,使用轻量级Ant风格表达式作为URL的匹配模式更为合适:

轻量级执行效率高
通配符(模式)符合正常理解,使用门槛非常低
*和**对层级路径/目录的支持感觉就是为此而生的
对于复杂场景亦可包含正常表达式来达到通用性
总的来讲,所谓为谁更好。Ant风格和正则表达式都有它们场景的最佳实践:

Ant风格:用于URL/目录这种标准分段式路径匹配
正则表达式:用于几乎没规律(或者规律性不强)的普通字符串匹配
AntPathMatcher:基于Ant风格的路径匹配器
PathMatcher接口并未规定路径匹配的具体方式,在Spring的整个技术栈里(包括Spring Boot和Cloud)有且仅有一个实现类AntPathMatcher:基于Ant风格的路径匹配器。它运用在Spring技术栈的方方面面,如:URL路径匹配、资源目录匹配等等。

这里有个有趣的现象:AntPathMatcher是Since:16.07.2003,而其接口PathMatcher是Since:1.2(2005.12)整整晚了2年+才出现。Spring当初的设想是把路径匹配抽象成为一种模式(也就是PathMatcher)而不限定具体实现,但奈何近20年过去了AntPathMatcher仍旧为PathMatcher接口的唯一实现。

说明:Spring 5新增了更高效的、设计更好的、全新的路径匹配器PathPattern,但它并未实现PathMatcher接口而是一套全新“生态”,用于逐步替换掉AntPathMatcher。关于此,下篇文章有详尽分析

说一千,道一万。了解PathMatcher/AntPathMatcher最为重要的是什么?当然是了解它的匹配规则,做到心里有数。安排,下面我就通过代码示例方式演示其匹配,尽量做到全乎,让你一文在手全部都有

public static void main(String[] args) {
        PathMatcher matcher = new AntPathMatcher();
        String pattern = "/xxx/**/*.txt";
        System.out.println(matcher.match(pattern, "/xxx/hello.txt")); // true
        System.out.println(matcher.match(pattern, "/xxx/yyy/hello.txt"));// true
        System.out.println(matcher.match(pattern, "/aaa/xxx/yyy/hello.txt")); //false
    }

值得注意的:

在配置白名单是不能加context-path(猜测是spring帮忙加了,加上匹配不对)

// 请求白名单 登录
    public static String URL_WHITE_ARRAY[];
    // 请求白名单 验证token 自定义过滤器
    public static List<UrlWhite> URL_WHITELIST = new ArrayList<>();

    static {
        URL_WHITELIST.add(new UrlWhite("/captcha"));
        URL_WHITELIST.add(new UrlWhite("/login"));
        URL_WHITELIST.add(new UrlWhite("/logout"));
        URL_WHITELIST.add(new UrlWhite("/password"));
        URL_WHITELIST.add(new UrlWhite("/image/**"));
        URL_WHITELIST.add(new UrlWhite("/sysUser/registerUser"));
        URL_WHITELIST.add(new UrlWhite("/hello/**"));
        URL_WHITELIST.add(new UrlWhite("/blogArticle/**", "GET"));
        URL_WHITELIST.add(new UrlWhite("/blogArticle/getBlogArticleListByPage"));
        URL_WHITELIST.add(new UrlWhite("/blogCategory/**", "GET"));
        URL_WHITELIST.add(new UrlWhite("/blogCategory/getBlogCategoryListByPage"));
        URL_WHITELIST.add(new UrlWhite("/blogTag/**", "GET"));
        URL_WHITELIST.add(new UrlWhite("/blogTag/getBlogTagListByPage"));
        URL_WHITE_ARRAY = URL_WHITELIST.stream().map(item -> item.getUrl()).toArray(size -> new String[size]);

    }

如果自己写需要加上context-path

// /dbd/blogTag/myBlogTagList
String requestURI = request.getRequestURI();
AntPathMatcher matcher = new AntPathMatcher();
for (UrlWhite whiteUrl : GlobalConstant.URL_WHITELIST) {
    if (matcher.match(GlobalConstant.CONTEXT_PATH + whiteUrl.getUrl(), requestURI)) {
        
    }
}

————————————————
版权声明:本文为CSDN博主「YourBatman」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/f641385712/article/details/118032869

  • 72
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值