重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
WebFlux中的Path参数解析与url映射是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
在分宜等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供做网站、网站设计 网站设计制作按需网站开发,公司网站建设,企业网站建设,品牌网站设计,成都全网营销推广,外贸网站建设,分宜网站建设费用合理。
WebFlux 之 Path 参数解析与 url 映射
异步、反应式、函数式编程,近来可以说是逐渐主流了;Spring5 通过 Reactor 增加了对反应式编程的支持,而 Spring WebFlux 不同于以往的 web 框架,作为一个非阻塞异步 web 框架,可以充分的利用多核 CPU 硬件资源,提供更强的并发支持;Spring 官方对 WebFlux 的支持非常友好,基本上对于惯于 Spring WEB 的 java 开发者,可以很简单的迁移过来
接下来我们将进入 WebFlux 系列教程,努力使用最简明的语言,来介绍一下 WebFlux 的基本玩法,让各位小伙伴可以顺畅的切换和使用 WebFlux 来体验反应式编程的魅力
小编将主要介绍 WebFlux 提供 web 接口时的 url 匹配,以及对应的 path 参数解析
本项目借助SpringBoot 2.2.1.RELEASE
+ maven 3.5.3
+ IDEA
进行开发
使用 WebFlux,最主要的引入依赖如下(省略掉了 SpringBoot 的相关依赖,如对于如何创建 SpringBoot 项目不太清楚的小伙伴,可以关注一下我之前的博文)
org.springframework.boot spring-boot-starter-webflux
下面所有内容基于官方文档完成: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-requestmapping-uri-templates
下面的示例主要是基于注解的方式,基本知识点和 SpringWeb 没有太大的区别(至于函数式的用法,后面会专门介绍)
path 参数,举例如: http://127.0.0.1:8080/name/test
中name
和test
就算是 path 参数,我们主要是借助@PathVariable
来获取
一个具体实例
@RestController @RequestMapping(path = "path") public class PathAction { /** * 最基本的path获取方式 * * @param index * @return */ @GetMapping(path = "/basic/{index}") public Monobasic(@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 Monobasic2(@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 路径依然需要正确匹配,多一级和少一级都不行
上面只有一个 path 参数,如果有多个参数,也比较简单
/** * 多个参数的场景 * * @param index * @param order * @return */ @GetMapping(path = "/mbasic/{index}/{order}") public Monombasic(@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%
上面的两个 case,都是完整的匹配某一级路径,下面介绍部分匹配的 case
/** * 路径中的部分内容匹配 * * - /part/test.txt -> name = test * - /part/a/test.txt -> 不匹配 * * @param name * @return */ @GetMapping(path = "/part/{name}.txt") public Monopart(@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"}%
接下来更高端的 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 MonourlPattern(@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); }
注意上面的所有写法,都有一个特点,那就是只能针对单级的 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 Monopattern2(@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%
前面介绍的是 path 参数解析,接下来我们简单的看一下最常见的三种路径匹配方式
一个星号,表示匹配 0 个 or1 个单级 path 路径
/** * 单个*号,只能匹配一级目录,注意这种方式与上面的 pattern2 之间的区别 * * 可以匹配: * * - /path/pattern3/hello * - /path/pattern3 * * 不能匹配 * * - /path/pattern3/hello/1 * * @return */ @GetMapping(path = "/pattern3/*") public Monopattern3() { 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 Monopattern33() { 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
不能
有别与上面的单个*
匹配 0-1 级 path 路径,两个**
则表示可以一直匹配到最后一层
/** * 对于 pattern4开头的都可以匹配 * * @return */ @GetMapping(path = "/pattern4/**") public Monopattern4() { 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
也是可以命中的,这个和上面是有区别的
单个字符的通配,比较简单如下
/** * 匹配 pattern5/test pattern5/tast ... * 不匹配 pattern5/tst pattern5/tesst * * @return */ @GetMapping(path = "/pattern5/t?st") public Monopattern5() { 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"}%
从上面的测试输出也可以看出
?
对应的地方不能是/
以及其他不被支持的字符(如?
,'
,"
, %
等)
?
对应的地方必须存在
虽然本文的主题是 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 |
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注创新互联行业资讯频道,感谢您对创新互联的支持。