【SpringBoot WebFlux 系列】WebFlux 之 Path 参数解析与 url 映射

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 接下来我们将进入 WebFlux 系列教程,努力使用最简明的语言,来介绍一下 WebFlux 的基本玩法,让各位小伙伴可以顺畅的切换和使用 WebFlux 来体验反应式编程的魅力本文将主要介绍 WebFlux 提供 web 接口时的 url 匹配,以及对应的 path 参数解析

image.png


异步、反应式、函数式编程,近来可以说是逐渐主流了;Spring5 通过 Reactor 增加了对反应式编程的支持,而 Spring WebFlux 不同于以往的 web 框架,作为一个非阻塞异步 web 框架,可以充分的利用多核 CPU 硬件资源,提供更强的并发支持;Spring 官方对 WebFlux 的支持非常友好,基本上对于惯于 Spring WEB 的 java 开发者,可以很简单的迁移过来


接下来我们将进入 WebFlux 系列教程,努力使用最简明的语言,来介绍一下 WebFlux 的基本玩法,让各位小伙伴可以顺畅的切换和使用 WebFlux 来体验反应式编程的魅力

本文将主要介绍 WebFlux 提供 web 接口时的 url 匹配,以及对应的 path 参数解析


I. 项目环境



本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发


1. 依赖


使用 WebFlux,最主要的引入依赖如下(省略掉了 SpringBoot 的相关依赖,如对于如何创建 SpringBoot 项目不太清楚的小伙伴,可以关注一下我之前的博文)


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
</dependencies>
复制代码


II. Path 匹配与参数解析



下面所有内容基于官方文档完成: docs.spring.io/spring/docs…

下面的示例主要是基于注解的方式,基本知识点和 SpringWeb 没有太大的区别(至于函数式的用法,后面会专门介绍)


1. 基本 path 参数获取


path 参数,举例如: http://127.0.0.1:8080/name/testnametest就算是 path 参数,我们主要是借助@PathVariable来获取


一个具体实例

@RestController
@RequestMapping(path = "path")
public class PathAction {
    /**
     * 最基本的path获取方式
     *
     * @param index
     * @return
     */
    @GetMapping(path = "/basic/{index}")
    public Mono<String> basic(@PathVariable(name = "index") int index) {
        return Mono.just("path index: " + index);
    }
}
复制代码


针对上面的 case,我们简单的设计了三个访问 case,具体结果如下

➜  ~ curl 'http://127.0.0.1:8080/path/basic/1'
path index: 1%
➜  ~ curl 'http://127.0.0.1:8080/path/basic/1/2'
{"timestamp":"2020-08-26T13:35:26.221+0000","path":"/path/basic/1/2","status":404,"error":"Not Found","message":null,"requestId":"8256bf73"}%
➜  ~ curl 'http://127.0.0.1:8080/path/basic/'
{"timestamp":"2020-08-26T13:35:32.196+0000","path":"/path/basic/","status":404,"error":"Not Found","message":null,"requestId":"eeda1111"}%
复制代码


请注意上面的输出,/basic/{index} 只能匹配单级的 path 路径参数,而且上面的写法中,这级 path 路径必须存在


查看PathVariable注解可以看到里面有一个required属性,如果设置为 false,会怎样呢

@GetMapping(path = "/basic2/{index}")
public Mono<String> basic2(@PathVariable(name = "index", required = false) Integer index) {
    return Mono.just("basic2 index: " + index);
}
复制代码


测试 case 如下

➜  ~ curl 'http://127.0.0.1:8080/path/basic2/'
{"timestamp":"2020-08-26T13:41:40.100+0000","path":"/path/basic2/","status":404,"error":"Not Found","message":null,"requestId":"b2729e2c"}%
➜  ~ curl 'http://127.0.0.1:8080/path/basic2/22'
basic2 index: 22%
➜  ~ curl 'http://127.0.0.1:8080/path/basic2/22/3'
{"timestamp":"2020-08-26T13:41:44.400+0000","path":"/path/basic2/22/3","status":404,"error":"Not Found","message":null,"requestId":"0b3f173c"}%
复制代码


从上面的实际 case,也可以看出来,级别这个属性设置为 false,但是 url 路径依然需要正确匹配,多一级和少一级都不行


2. 多 path 参数


上面只有一个 path 参数,如果有多个参数,也比较简单


/**
 * 多个参数的场景
 *
 * @param index
 * @param order
 * @return
 */
@GetMapping(path = "/mbasic/{index}/{order}")
public Mono<String> mbasic(@PathVariable(name = "index") int index, @PathVariable(name = "order") String order) {
    return Mono.just("mpath arguments: " + index + " | " + order);
}
复制代码


测试 case 如下

➜  ~ curl 'http://127.0.0.1:8080/path/mbasic/1/asc'
mpath arguments: 1 | asc%
复制代码


3. 部分 path 参数匹配


上面的两个 case,都是完整的匹配某一级路径,下面介绍部分匹配的 case


/**
 * 路径中的部分内容匹配
 *
 * - /part/test.txt -> name = test
 * - /part/a/test.txt -> 不匹配
 *
 * @param name
 * @return
 */
@GetMapping(path = "/part/{name}.txt")
public Mono<String> part(@PathVariable(name = "name") String name) {
    return Mono.just("part path argument: " + name);
}
复制代码


请注意上面的 path 路径,后缀是.txt,如下面的实例中part/hello.txt中那么对应的就是hello


➜  ~ curl 'http://127.0.0.1:8080/path/part/hello.txt'
part path argument: hello%
➜  ~ curl 'http://127.0.0.1:8080/path/part/hello.tx'
{"timestamp":"2020-08-26T13:47:49.121+0000","path":"/path/part/hello.tx","status":404,"error":"Not Found","message":null,"requestId":"1075d683"}%
复制代码


4. 正则匹配


接下来更高端的 path 参数匹配来了,支持一些简单的正则,如我们希望对spring-web-3.0.5.jar这段 path 路径进行解析,希望将spring-web作为name, 3.0.5作为version.jar作为ext


因此我们的 rest 接口写法可以如下

/**
 * 正则匹配
 *
 * /path/path/pattern/spring-web-3.0.5.jar  -> name = spring-web,  version=3.0.5,  ext=.jar
 *
 * @return
 */
@GetMapping(path = "/pattern/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public Mono<String> urlPattern(@PathVariable(name = "name") String name,
        @PathVariable(name = "version") String version, @PathVariable(name = "ext") String ext) {
    return Mono.just("pattern arguments name=" + name + " version=" + version + " ext=" + ext);
}
复制代码


5. 多级 path 参数匹配


注意上面的所有写法,都有一个特点,那就是只能针对单级的 path 路径进行全/部分匹配(本文中将 path 路径中//之间作为一级),那么如果我希望我的 path 参数可以匹配多级,可以怎么办


  • /path/name/hello 请求路径中,我希望将 /name/hello 作为一个 path 参数

针对上面的场景,我们主要是借助{*name}方式来处理,注意这个参数名前面的*号

/**
 * 匹配:
 *
 * - /path/pattern2  -> name == ""
 * - /path/pattern2/hello  -> name == /hello
 * - /path/pattern2/test/hello -> name = /test/hello
 *
 * @param name
 * @return
 */
@GetMapping(path = "/pattern2/{*name}")
public Mono<String> pattern2(@PathVariable(name = "name") String name) {
    return Mono.just("pattern2 argument: " + name);
}
复制代码


测试 case 如下

➜  ~ curl 'http://127.0.0.1:8080/path/pattern2'
pattern2 argument: %
➜  ~ curl 'http://127.0.0.1:8080/path/pattern2/hello'
pattern2 argument: /hello%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern2/hello/world'
pattern2 argument: /hello/world%
复制代码


6. 路径匹配


前面介绍的是 path 参数解析,接下来我们简单的看一下最常见的三种路径匹配方式

a. *


一个星号,表示匹配 0 个 or1 个单级 path 路径

/**
 * 单个*号,只能匹配一级目录,注意这种方式与上面的 pattern2 之间的区别
 *
 * 可以匹配:
 *
 * - /path/pattern3/hello
 * - /path/pattern3
 *
 * 不能匹配
 *
 * - /path/pattern3/hello/1
 *
 * @return
 */
@GetMapping(path = "/pattern3/*")
public Mono<String> pattern3() {
    return Mono.just("pattern3 succeed!");
}
复制代码


实测 case 如下


# 请注意,这里是没有/结尾的
➜  ~ curl 'http://127.0.0.1:8080/path/pattern3'
{"timestamp":"2020-08-27T00:01:20.703+0000","path":"/path/pattern3","status":404,"error":"Not Found","message":null,"requestId":"c88f5066"}%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern3/'
pattern3 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern3/a'
pattern3 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern3/a/b'
{"timestamp":"2020-08-27T00:01:18.144+0000","path":"/path/pattern3/a/b","status":404,"error":"Not Found","message":null,"requestId":"203dc7d4"}%
复制代码


请注意上面的实例,/path/pattern3 访问 404, 而/path/pattern3/是可以的,唯一的区别就是多了一个后缀/


  • why?
  • 是因为 path 路径的星号前面有一个/导致的么?


接下来我们再设计一个 case,将*前面的/干掉,再测试一下


@GetMapping(path = "/pattern33**")
public Mono<String> pattern33() {
    return Mono.just("pattern33 succeed!");
}
复制代码


再次测试,结果如下

➜  ~ curl 'http://127.0.0.1:8080/path/pattern3311'
pattern33 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern33/11'
{"timestamp":"2020-08-27T00:05:51.236+0000","path":"/path/pattern33/11","status":404,"error":"Not Found","message":null,"requestId":"d8cbd546"}%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern33'
pattern33 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern331/'
pattern33 succeed!%
复制代码


借助前面两个 case,我们基本上可以看出*的作用


  • *前面的完全匹配
  • 比如/pattern3/*,那么访问的 path 路径前缀必须是/pattern3/
  • *最多表示单级路径,简单来讲就是*所代表的的位置中不能出现/x
  • 比如/pattern33**,那么/pattern331/可以匹配,但是/pattern331/1不能


b. **


有别与上面的单个*匹配 0-1 级 path 路径,两个**则表示可以一直匹配到最后一层

/**
 * 对于 pattern4开头的都可以匹配
 *
 * @return
 */
@GetMapping(path = "/pattern4/**")
public Mono<String> pattern4() {
    return Mono.just("pattern4 succeed!");
}
复制代码


测试 case 如下

➜  ~ curl 'http://127.0.0.1:8080/path/pattern4'
pattern4 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern4/12'
pattern4 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern4/12/3'
pattern4 succeed!%
复制代码


请注意


  • 直接访问/pattern4也是可以命中的,这个和上面是有区别的


c. ?


单个字符的通配,比较简单如下

/**
 * 匹配  pattern5/test   pattern5/tast ...
 * 不匹配 pattern5/tst pattern5/tesst
 *
 * @return
 */
@GetMapping(path = "/pattern5/t?st")
public Mono<String> pattern5() {
    return Mono.just("pattern5 succeed!");
}
复制代码


访问 case

➜  ~ curl 'http://127.0.0.1:8080/path/pattern5/test'
pattern5 succeed!%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern5/t/st'
{"timestamp":"2020-08-27T00:13:42.557+0000","path":"/path/pattern5/t/st","status":404,"error":"Not Found","message":null,"requestId":"add34639"}%
➜  ~ curl 'http://127.0.0.1:8080/path/pattern5/tst'
{"timestamp":"2020-08-27T00:14:01.078+0000","path":"/path/pattern5/tst","status":404,"error":"Not Found","message":null,"requestId":"b2691121"}%
复制代码


从上面的测试输出也可以看出


  • ? 对应的地方不能是/以及其他不被支持的字符(如?,',", %等)
  • ? 对应的地方必须存在


7. 小结


虽然本文的主题是 webflux 中 path 参数解析与 url 映射匹配,但是看下来我们会神奇的发现,这些知识点和 SpringMVC 中,貌似也没有什么区别,事实上也确实如此;对于注解的使用场景时,绝大多数,都是之前怎么玩,现在依然可以怎么玩


下面用一个表格针对上面的知识点进行汇总

pattern 描述 举例
? 匹配一个字符 pages/t?st.html 匹配 /pages/test.html and /pages/t3st.html
* 匹配单级 path 路径中 0-多个字符 "/resources/*.png" matches "/resources/file.png"
"/projects/*/versions" matches "/projects/spring/versions" but does not match "/projects/spring/boot/versions"
** 匹配 0-多个 path 路径 "/resources/**" matches "/resources/file.png" and "/resources/images/file.png"
"/resources/**/file.png"这种写法是非法的
{name} 匹配单级 path 路径参数 "/projects/{project}/versions" matches "/projects/spring/versions" and captures project=spring
{name:[a-z]+} 正则 "/projects/{project:[a-z]+}/versions" matches "/projects/spring/versions" but not "/projects/spring1/versions"
{*path} 匹配 path 路径中,0-最后一级 path 路径参数 "/resources/{*file}" matches "/resources/images/file.png" and captures file=images/file.png



相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
221 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
224 2
|
2月前
|
NoSQL Java MongoDB
Springboot WebFlux项目结合mongodb进行crud
这篇文章介绍了如何使用Spring Boot WebFlux框架结合MongoDB进行基本的CRUD(创建、读取、更新、删除)操作,包括项目设置、实体类和Repository的创建、控制器的实现以及配置文件的编写。
55 0
Springboot WebFlux项目结合mongodb进行crud
|
3天前
|
JSON 自然语言处理 Java
OpenAI API深度解析:参数、Token、计费与多种调用方式
随着人工智能技术的飞速发展,OpenAI API已成为许多开发者和企业的得力助手。本文将深入探讨OpenAI API的参数、Token、计费方式,以及如何通过Rest API(以Postman为例)、Java API调用、工具调用等方式实现与OpenAI的交互,并特别关注调用具有视觉功能的GPT-4o使用本地图片的功能。此外,本文还将介绍JSON模式、可重现输出的seed机制、使用代码统计Token数量、开发控制台循环聊天,以及基于最大Token数量的消息列表限制和会话长度管理的控制台循环聊天。
33 7
|
2天前
|
存储 Java API
在springboot中缩短一个url链接
URL缩短服务是现代应用中常见的需求,用于将长URL映射为简短的唯一代码,便于分享。该服务具备多种功能,如自动过期、访问统计、防止重复及安全机制。通过Spring Boot构建RESTful API,使用H2数据库存储数据,Java UUID生成短码,并通过定时任务清理过期URL。用户可通过API提交长URL获取短链接,查询访问量,系统会自动重定向并记录访问次数。每天午夜自动清理过期URL,确保数据整洁。此项目结构清晰,涵盖实体类、Repository、Service和Controller等核心组件,适合快速开发和扩展。
|
23天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
68 2
|
24天前
|
前端开发 Java Maven
深入解析:如何用 Spring Boot 实现分页和排序
深入解析:如何用 Spring Boot 实现分页和排序
47 2
|
26天前
|
域名解析 网络协议 安全
反向DNS解析是从IP地址到域名的映射,主要作用于验证和识别,提高通信来源的可信度和可追溯性
在网络世界中,反向DNS解析是从IP地址到域名的映射,主要作用于验证和识别,提高通信来源的可信度和可追溯性。它在邮件服务器验证、网络安全等领域至关重要,帮助识别恶意行为,增强网络安全性。尽管存在配置错误等挑战,但正确管理下,反向DNS解析能显著提升网络环境的安全性和可靠性。
90 3
|
1月前
|
域名解析 缓存 网络协议
浏览器中输入URL返回页面过程(超级详细)、DNS域名解析服务,TCP三次握手、四次挥手
浏览器中输入URL返回页面过程(超级详细)、DNS域名解析服务,TCP三次握手、四次挥手
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
483 37

推荐镜像

更多
下一篇
DataWorks