【SpringBoot】当 @PathVariable 遇到 /,如何处理
1. 问题复现
-
在解析一个 URL 时,我们经常会使用 @PathVariable 这个注解。例如我们会经常见到如下风格的代码:
@RestController @Slf4j public class HelloWorldController {@RequestMapping(path = "/hi1/{name}", method = RequestMethod.GET)public String hello1(@PathVariable("name") String name){return name;}; } -
当我们使用 http://localhost:8080/hi1/xiaoming 访问这个服务时,会返回"xiaoming",即 Spring 会把 name 设置为 URL 中对应的值。
-
看起来顺风顺水,但是假设这个 name 中含有特殊字符 / 时(例如http://localhost:8080/hi1/xiao/ming ),会如何?如果我们不假思索,或许答案是"xiao/ming"?然而稍微敏锐点的程序员都会判定这个访问是会报错的,具体错误参考:

-
如图所示,当 name 中含有 /,这个接口不会为 name 获取任何值,而是直接报 Not Found 错误。当然这里的“找不到”并不是指 name 找不到,而是指服务于这个特殊请求的接口。
-
实际上,这里还存在另外一种错误,即当 name 的字符串以 / 结尾时,/ 会被自动去掉。例如我们访问 http://localhost:8080/hi1/xiaoming/,Spring 并不会报错,而是返回 xiaoming。
-
针对这两种类型的错误,应该如何理解并修正呢?
2. 案例解析
- 实际上,这两种错误都是 URL 匹配执行方法的相关问题,所以我们有必要先了解下 URL 匹配执行方法的大致过程。参考 AbstractHandlerMethodMapping#lookupHandlerMethod:
@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();//尝试按照 URL 进行精准匹配List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {//精确匹配上,存储匹配结果addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {//没有精确匹配上,尝试根据请求来匹配addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);Match bestMatch = matches.get(0);if (matches.size() > 1) {//处理多个匹配的情况}//省略其他非关键代码return bestMatch.handlerMethod;}else {//匹配不上,直接报错return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);} - 大体分为这样几个基本步骤。
1. 根据 path 进行精确匹配
- 这个步骤执行的代码语句是
this.mappingRegistry.getMappingsByUrl(lookupPath),实际上,它是查询MappingRegistry#urlLookup,它的值可以用调试视图查看,如下图所示:

- 查询 urlLookup 是一个精确匹配 Path 的过程。很明显,http://localhost:8080/hi1/xiao/ming 的 lookupPath 是"/hi1/xiao/ming",并不能得到任何精确匹配。这里需要补充的是,"/hi1/{name}"这种定义本身也没有出现在 urlLookup 中。
2. 假设 path 没有精确匹配上,则执行模糊匹配
- 在步骤 1 匹配失败时,会根据请求来尝试模糊匹配,待匹配的匹配方法可参考下图:

- 显然,"/hi1/{name}"这个匹配方法已经出现在待匹配候选中了。具体匹配过程可以参考方法
RequestMappingInfo#getMatchingCondition:public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);if (methods == null) {return null;}ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);if (params == null) {return null;}//省略其他匹配条件PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);if (patterns == null) {return null;}//省略其他匹配条件return new RequestMappingInfo(this.name, patterns,methods, params, headers, consumes, produces, custom.getCondition()); } - 现在我们知道匹配会查询所有的信息,例如 Header、Body 类型以及 URL 等。如果有一项不符合条件,则不匹配。
- 在我们的案例中,当使用 http://localhost:8080/hi1/xiaoming 访问时,其中 patternsCondition 是可以匹配上的。实际的匹配方法执行是通过 AntPathMatcher#match 来执行,判断的相关参数可参考以下调试视图:

- 但是当我们使用 http://localhost:8080/hi1/xiao/ming 来访问时,AntPathMatcher 执行的结果是"/hi1/xiao/ming"匹配不上"/hi1/{name}"。
3. 根据匹配情况返回结果
-
如果找到匹配的方法,则返回方法;如果没有,则返回 null。
-
在本案例中,http://localhost:8080/hi1/xiao/ming 因为找不到匹配方法最终报 404 错误。追根溯源就是 AntPathMatcher 匹配不了"/hi1/xiao/ming"和"/hi1/{name}"。
-
另外,我们再回头思考 http://localhost:8080/hi1/xiaoming/ 为什么没有报错而是直接去掉了 /。这里我直接贴出了负责执行 AntPathMatcher 匹配的
PatternsRequestCondition#getMatchingPattern方法的部分关键代码:private String getMatchingPattern(String pattern, String lookupPath) {//省略其他非关键代码if (this.pathMatcher.match(pattern, lookupPath)) {return pattern;}//尝试加一个/来匹配if (this.useTrailingSlashMatch) {if (!pattern.endsWith("/") && this.pathMatcher.match(pattern + "/", lookupPath)) {return pattern + "/";}}return null; } -
在这段代码中,AntPathMatcher 匹配不了"/hi1/xiaoming/“和”/hi1/{name}",所以不会直接返回。进而,在 useTrailingSlashMatch 这个参数启用时(默认启用),会把 Pattern 结尾加上 / 再尝试匹配一次。如果能匹配上,在最终返回 Pattern 时就隐式自动加 /。
-
很明显,我们的案例符合这种情况,等于说我们最终是用了"/hi1/{name}/“这个 Pattern,而不再是”/hi1/{name}"。所以自然 URL 解析 name 结果是去掉 / 的。
3. 问题修正
-
针对这个案例,有了源码的剖析,我们可能会想到可以先用"**"匹配上路径,等进入方法后再尝试去解析,这样就可以万无一失吧。具体修改代码如下:
@RequestMapping(path = "/hi1/**", method = RequestMethod.GET) public String hi1(HttpServletRequest request){String requestURI = request.getRequestURI();return requestURI.split("/hi1/")[1]; }; -
但是这种修改方法还是存在漏洞,假设我们路径的 name 中刚好又含有"/hi1/",则 split 后返回的值就并不是我们想要的。实际上,更合适的修订代码示例如下:
private AntPathMatcher antPathMatcher = new AntPathMatcher();@RequestMapping(path = "/hi1/**", method = RequestMethod.GET) public String hi1(HttpServletRequest request){String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);//matchPattern 即为"/hi1/**"String matchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE); return antPathMatcher.extractPathWithinPattern(matchPattern, path); }; -
经过修改,两个错误都得以解决了。当然也存在一些其他的方案,例如对传递的参数进行 URL 编码以避免出现 /,或者干脆直接把这个变量作为请求参数、Header 等,而不是作为 URL 的一部分。你完全可以根据具体情况来选择合适的方案。
相关文章:
【SpringBoot】当 @PathVariable 遇到 /,如何处理
1. 问题复现 在解析一个 URL 时,我们经常会使用 PathVariable 这个注解。例如我们会经常见到如下风格的代码: RestController Slf4j public class HelloWorldController {RequestMapping(path "/hi1/{name}", method RequestMethod.GET)publ…...
【FlutterDart】页面切换 PageView PageController(9 /100)
上效果: 有些不能理解官方例子里的动画为什么没有效果,有可能是我写法不对 后续如果有动画效果修复了,再更新这篇,没有动画效果,总觉得感受的丝滑效果差了很多 上代码: import package:flutter/material.…...
Backend - C# 的日志 NLog日志
目录 一、注入依赖和使用 logger 二、配置记录文件 1.安装插件 NLog 2.创建 nlog.config 配置文件 3. Programs配置日志信息 4. 设置 appsettings.json 的 LogLevel 5. 日志设定文件和日志级别的优先级 (1)常见的日志级别优先级 (2&…...
Flask是什么?深入解析 Flask 的设计与应用实践
文章目录 一、引言:从微框架到生态系统二、Flask 的核心设计理念三、Flask 的关键组件解析3.1 路由系统3.2 请求与响应对象3.3 模板引擎 Jinja23.4 扩展系统 四、Flask 的并发与性能优化4.1 默认的单线程模型4.2 提升并发性能的方法4.3 性能优化技巧 五、在企业级场…...
malloc函数和calloc函数的区别是什么?
malloc函数和calloc函数在动态内存管理中都起着分配内存空间的作用,但它们存在以下区别: 参数方面 - malloc函数:它只有一个参数,该参数表示要分配的字节数。例如, int *ptr (int *)malloc(10 * sizeof(int)); &#…...
Ansys Maxwell:3PH 变压器电感计算
各位变形金刚粉丝们,大家好: 在本博客中,我讨论了如何使用 Ansys Maxwell 计算三相变压器中的自感、互感和漏感。有多种方法和表达式可用于计算这些电感。 基本电感定义 电感的单位是亨利(H),其基本单位…...
【Go】Go文件操作详解
1. 前言 相信如果看过之前文章的朋友们一定知道我想讲什么了?灵魂三问:文件是什么?为什么需要文件?文件怎么操作?前面章节我们已经能够编写各种各样的功能代码了,但是一个很现实的问题就是我们没有任何 持…...
[react+ts] useRef获取自定义组件dom或方法声明
想用useRef获取自定义组件? 如果获取dom,直接写 const sonRef useRef<HTMLDivElement>(null); 然后子组件用forwardRef包一层,注意是HTMLDivElement,别写错, 写HTMLElement不行 const Son forwardRef<HTMLDivElement, IProps>((props, ref) > {}) 切记这…...
AI 将在今年获得“永久记忆”,2028美国会耗尽能源储备
AI的“永久记忆”时代即将来临 谷歌前CEO施密特揭示了AI技术的前景,他相信即将在2025年迎来一场伟大的变化。AI将实现“永久记忆”,改变我们与科技的互动过程。施密特将现有的AI上下文窗口比作人类的短期记忆,难以持久保存信息。他的设想是…...
【视频笔记】基于PyTorch从零构建多模态(视觉)大模型 by Umar Jamil【持续更新】
视频链接: 基于PyTorch从零构建多模态(视觉)大模型 by Umar Jamil 从头编写一个视觉语言模型:PloyGamma,是谷歌的一个模型 1:原始图像 2:视觉编码器(本文是viT),通过对比学习进行训练。这个对比学习最开始是CLIP,后来被谷歌改成了SigLIP 3:线性投影层 4:如何将图…...
解决 C++ 中头文件相互引用和解耦问题
在 C 中,当多个 .h 文件相互引用时,可能会导致 循环依赖 或 头文件冗余 问题,进而引发编译时间延迟、代码复杂度增加等问题。为了有效地解耦和组织代码,可以采用以下几种策略和思想: 1. 前向声明(Forward …...
河马剧场(短剧)APP的邀请码怎么填写
上篇给大家说到河马剧场免费看短剧还能领5.2元3天vip会员,本文就说一下河马剧场河马短剧APP的邀请码怎么填写。 河马短剧APP填写邀请码分三步: 1、安装登陆河马短剧APP 2、点击底部导航栏中间的“福利” 3、往下划会看到“填写邀请码领3天vip” 4、…...
01:C语言的本质
C语言的本质 1、ARM架构与汇编2、局部变量初始化与空间分配2.1、局部变量的初始化2.1、局部变量数组初始化 3、全局变量/静态变量初始化化与空间分配4、堆空间 1、ARM架构与汇编 ARM简要架构如下:CPU,ARM(能读能写),Flash(能读&a…...
第1章:数据库基础
第1章:数据库基础 1.1 数据库概述 1.1.1 什么是数据库 数据库的定义数据库的发展历程数据库的重要性 1.1.2 关系型数据库简介 关系型数据库模型常见的关系型数据库关系型数据库的特点 1.1.3 MySQL在企业中的应用 Web应用电商平台金融系统大数据存储 1.2 数据…...
C++教程 | string类的定义和初始化方法
在C中,string是标准库中用于处理字符串的类,定义在 头文件中,它提供了方便、灵活的字符串操作功能。以下是一些常见的定义和初始化string对象的方法: 1. 默认初始化 可以直接定义一个空的string对象,语法如下&#x…...
React中的合成事件
合成事件与原生事件 区别: 1. 命名不一样,原生用纯小写方式,react用小驼峰的方式 原生:onclick React的:onClick 2. 事件处理函数的写法不一样 原生的是传入一个字符串,react写法传入一个回调函数 3.…...
[SMARTFORMS] 创建FORM
输入事务码SMARTFORMS进入表单开发界面,选中表单,自定义表单名称ZFS_DEMO_2025 点击"创建"按钮,跳转至"SAP表格设计器"页面 在"表格属性"填写表单描述、指定页格式和样式 在"表格接口"可以填写SMART…...
成都和力九垠科技有限公司九垠赢系统Common存在任意文件上传漏洞
免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...
基于Python的考研学习系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
『SQLite』几种向表中插入数据的方法
向表中插入数据 INSERT INTO 语句用来给数据库中的某个表中新增数据行。 案例 直接根据基本语法插入数据插入时不用全部指定列名方式根据查询结果将数据插入另一张表中 注意 上述内容详讲见文章:SQLite的INSERT操作(内含案例)...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
