百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Spring5 里边的新玩法!这种 URL 请求让我涨见识了

zhezhongyun 2025-03-11 23:47 78 浏览

Spring5 也已经出来好久了,里边有一些新玩法也需要我们去慢慢揭开面纱,这不,松哥最近在研究 SpringMVC 源码的时候,就看到这样一段代码:

protected String initLookupPath(HttpServletRequest request) {
 if (usesPathPatterns()) {
  request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
  RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
  String lookupPath = requestPath.pathWithinApplication().value();
  return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
 }
 else {
  return getUrlPathHelper().resolveAndCacheLookupPath(request);
 }
}

这个方法就是 Spring5 里边出来的,以前是没有这个方法的。在旧的 SpringMVC 中,当我们需要获取当前请求地址的时候,直接通过如下方式获取:

String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);

但是现在变了,现在获取当前请求 URL 地址时,方式如下:

String lookupPath = initLookupPath(request);

两种方式相比,主要是 initLookupPath 方法中多了 usesPathPatterns 选项,这是 Spring5 中的新玩意,所以今天松哥就通过一篇简单的文章来和大家分享一下 usesPathPatterns 到底是什么,该怎么玩!

这可不是一个小变化哦!特别是如果你在项目中使用了 WebFlux,那么这个东西就显得尤为重要了!

AntPathMatcher

当我们使用 @RequestMapping 注解去标记请求接口的时候(或者使用它的类似方法如 @GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping),我们可以使用一些通配符去匹配 URL 地址,举个简单例子,假设我有下面五个接口:

@GetMapping("/hello/**/hello")
public String hello() {
    return "/hello/**/hello";
}
@GetMapping("/h?llo")
public String hello2() {
    return "/h?llo";
}
@GetMapping("/**/*.html")
public String hello3() {
    return "/**/*.html";
}
@GetMapping("/hello/{p1}/{p2}")
public String hello4(@PathVariable String p1, @PathVariable String p2) {
    System.out.println("p1 = " + p1);
    System.out.println("p2 = " + p2);
    return "/hello/{p1}/{p2}";
}
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
    System.out.println("name = " + name);
    System.out.println("version = " + version);
    System.out.println("ext = " + ext);
}

在解释接口的含义之前,先来说说这几个通配符的含义:

通配符 含义 ** 匹配0个或者多个目录 * 匹配0个或者多个字符 ? 匹配任意单个字符

了解了通配符的含义,我们再来说说各个接口都能接收哪些请求:

  • 第一个接口,可以接收诸如 /hello/123/123/hello/hello/a/hello 以及 /hello/hello 这样的请求,因为中间的 ** 代表 0 个或者多个目录。
  • 第二个接口,可以接收诸如 /hallo/hello/hMllo 之类的请求,注意它不能接收 /haallo 或者 /hllo,因为 ? 表示一个字符。
  • 第三个接口可以接收任意以 .html 为后缀的请求,例如 /aaa/bb/cc.html/aa.html 或者 /aa/aa.html
  • 第四个接口估计大家都比较熟悉,在 RESTful 风格的接口设计中估计大家都用过,它接收的请求格式类似于 /hello/aa/bb,其中参数 p1 就对应 aa,参数 p2 对应 bb。
  • 第五个接口则用到了正则,name、version 以及 ext 三个参数格式用正则表达出来,它可以接收诸如 /spring-web-3.0.5.jar 格式的请求,最终的参数 name 就是 spring-web,version 就是 3.0.5,ext 则是 .jar

这是 SpringMVC 中之前就存在的功能,不管你用没用过,反正它一致存在。

那么是谁支撑了这个功能呢?那就是 AntPathMatcher。

AntPathMatcher 是一个实现了 Ant 风格的路径匹配器,Ant 风格的路径规则实际上就是我们前面给大家介绍的那三种路径匹配符,很 Easy。这种路径匹配规则源自 Apache Ant 项目(https://ant.apache.org),Apache Ant 我们现在其实已经很少会用到了,它的替代品就是大家所熟知的 Maven,如果你有幸维护一些 2010 年之前的老项目的话,有可能会接触到 Ant。

AntPathMatcher 实际上在 SpringMVC 中有非常广泛的应用,不仅仅是在 @RequestMapping 中定义接口用到,在其他一些涉及到地址匹配的地方也会用到,例如我们在 SpringMVC 的配置文件中配置静态资源过滤时,也是 Ant 风格路径匹配:


另外像拦截器里的拦截路径注册、跨域处理时的路径匹配等等,都会用到 Ant 风格的路径匹配符。

整体上来说,AntPathMatcher 是 Spring 中一种比较原始的路径匹配解决方案,虽然比较简单,但是它的效率很低,并且在处理 URL 编码的时候也很不方便。

因此,才有了 Spring5 中的 PathPattern。

PathPattern

PathPattern 专为 Web 应用设计,它与之前的 AntPathMatcher 功能大部分比较类似,当然也有一些细微差异,这个松哥后面会说。

如果是 Servlet 应用,目前官方推荐的 URL 匹配解决方案就是 PathPattern(当然你也可以选择较早的 AntPathMatcher),虽然官方推荐的是 PathPattern,但实际上默认使用的依然是 AntPathMatcher;如果你用的是 WebFlux,PathPattern 就是唯一解决方案了。

注意,PathPattern 是一个非常新鲜的玩艺,目前 Spring 最新版是 5.3.4,在 Spring5.3 之前,我们在 Servlet 应用中,也只能选择 AntPathMatcher,从 Spring5.3 之后,我们才可以使用 PathPattern 了。

PathPattern 会将 URL 规则预解析为 PathContainer,它对 URL 地址匹配的处理更加快速,PathPattern 与 AntPathMatcher 的差异主要体现在两个方面:

第一,PathPattern 只支持结尾部分使用 **,如果在路径的中间使用 ** 就会报错,上文中第一个和第三个接口,在 PathPattern 模式下会报错,如下:

因为在中间或者开始使用 ** 极易造成混乱,因此 PathPattern 只支持在结尾使用 **

第二,PathPattern 支持使用诸如 {*path} 的方式进行路径匹配,这种写法也可以匹配到多层路径,并且将匹配到的值赋值给 path 变量,例如如下一个接口:

@GetMapping("/javaboy/{*path}")
public void hello6(@PathVariable String path) {
    System.out.println("path = " + path);
}

如果请求路径是
http://localhost:8080/javaboy/aa
,那么参数 path 的值就是 /aa

如果请求路径是
http://localhost:8080/javaboy/aa/bb/cc/dd
,那么参数 path 的值就是 /aa/bb/cc/dd

这个写法也比较新颖,因为之前的 AntPathMatcher 里边没有这个。

如何使用

默认情况下,SpringMVC 中使用的还是 AntPathMatcher,那么如何开启 PathPattern 呢?很简单,在 SpringBoot 项目中只需要添加如下配置即可:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setPatternParser(new PathPatternParser());
    }
}

添加了这个配置后,在我们文章一开始贴出来的代码里,就会进入到 if 分支中,进而使用 PathPattern 去解析请求 URL。

小结

好啦,今天就和小伙伴们聊这么多,大家可以体验一把这个东东,不过注意选择 Spring 的版本哦,一定选择 5.3 之上的版本~大家周末愉快哦~

相关推荐

Python入门学习记录之一:变量_python怎么用变量

写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...

python变量命名规则——来自小白的总结

python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...

Python入门学习教程:第 2 章 变量与数据类型

2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...

绘制学术论文中的“三线表”具体指导

在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...

Python基础语法知识--变量和数据类型

学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...

一文搞懂 Python 中的所有标点符号

反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...

Python变量类型和运算符_python中变量的含义

别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...

从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序

在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...

Python中下划线 ‘_’ 的用法,你知道几种

Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...

解锁Shell编程:变量_shell $变量

引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...

一文学会Python的变量命名规则!_python的变量命名有哪些要求

目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...

更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for

src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...

C++第五课:变量的命名规则_c++中变量的命名规则

变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....

Rust编程-核心篇-不安全编程_rust安全性

Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...

探秘 Python 内存管理:背后的神奇机制

在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...