先记录下@PathVariable的用法吧:

 @RequestMapping("/demo/{id}")
@ResponseBody
public User getUser(@PathVariable("id")Integer id, HttpServletRequest request){
System.out.println(request.getAttribute(RequestMappingHandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE));
List<User> list=new ArrayList<>();
list.add(new User(0,"A"));
list.add(new User(1,"B"));
list.add(new User(2,"C"));
list.add(new User(3,"D"));
User user = list.get(id);
return user;
}

使用方式一:就像上面那样{}代表占位符,匹配URL中/ /两个之间的内容,通过@PathVariable进行解析

使用方式二:通过request的RequestMappingHandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE这个属性获取到一个Map,然后根据上面的key进行取值

有时候很好奇Spring @Pathvariable怎么解析的,好像无论多少个 {} 都能正确的映射,看起来好像没那么难。 但是我脑子不太行,尝试分析分析看看吧。

就像以前做笔试题:正确答案在下面,虽然我肯定写不出来,但是能看懂也挺为难我哈哈哈哈。

就像给定两个输入, pattern是标准路径 ,就像 /url/{id} , 而lookupPath就是请求路径,就像/url/19 ;

如果pattern和lookupPath一样,就直接返回,这个不难理解,常规URL映射都是这么映射的;

考虑到实际情况以及简化分析,useSuffixPatternMatch 默认为 true , fileExtensions 默认为空 ,以不带后缀名形式分析 ,那就会进入AntPathMatcher分析;

protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) {
//pattern为标准路径 /url/{id} path为请求request路径/url/19
//fullMatch默认为true ; uriTemplateVariables默认为null
//pathSeparator默认为 /
//path和pattern刚开始都是/ 开头, 肯定是false ; 这一步算是规则校验 路径不以/开头的 直接返回false
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
//标准路径/url/{id}分隔成字符串数组 pattDirs
String[] pattDirs = tokenizePattern(pattern);
//isPotentialMatch方法:
//请求路径 和 @RequestMapping路径匹配 从头匹配刚开始就不相等直接返回false
//解析过程前面相等遇到 { * ?类型返回true 这里逻辑等等再具体描述
if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
return false;
}
//请求路径拆分成字符串数组pathDirs
String[] pathDirs = tokenizePath(path); int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1; //循环遍历是否 请求路径字符数组 和 @RequestMapping路径数组 正则匹配
//{id}的正则表达式被解析为 (.*) 肯定可以匹配上
//字符数组只要有一个元素没匹配上就返回false
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
if ("**".equals(pattDir)) {
break;
}
if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
//上面如果匹配完成,pathIdxStart=pathIdxEnd+1 pattIdxStart=pattIdxEnd+1
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
if (pattIdxStart > pattIdxEnd) {
// /url/{id} /url/19 就匹配上了到这里 返回true
return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
!path.endsWith(this.pathSeparator));
}
if (!fullMatch) {
return true;
}
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
else if (pattIdxStart > pattIdxEnd) {
// String not exhausted, but pattern is. Failure.
return false;
}
else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// Path start definitely matches due to "**" part in pattern.
return true;
} // up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxEnd];
if (pattDir.equals("**")) {
break;
}
if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) {
// String is exhausted
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
} while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
int patIdxTmp = -1;
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
if (pattDirs[i].equals("**")) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == pattIdxStart + 1) {
// '**/**' situation, so skip one
pattIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - pattIdxStart - 1);
int strLength = (pathIdxEnd - pathIdxStart + 1);
int foundIdx = -1; strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
String subPat = pattDirs[pattIdxStart + j + 1];
String subStr = pathDirs[pathIdxStart + i + j];
if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
} if (foundIdx == -1) {
return false;
} pattIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
} for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
} return true;
}

isPotentialMatch:进一步的过滤规则

private boolean isPotentialMatch(String path, String[] pattDirs) {
// path 请求路径 pattDirs标准路径以 / 分隔出来的字符数组
if (!this.trimTokens) {
//请求路径转成char数组
char[] pathChars = path.toCharArray();
int pos = 0;
for (String pattDir : pattDirs) {
//请求路径中第一次从0开始找到/的位置,下次就从上次找到的位置开始找下一个/
int skipped = skipSeparator(path, pos, this.pathSeparator);
pos += skipped;
//skipSegment从pathChars找出跳过pattDir的长度
skipped = skipSegment(pathChars, pos, pattDir);
//skipped最理想情况等于pattDir的长度 但是通常通配符形式这里都是小于
  //比如映射中包含demo,路径为do,这时候skipped也是2 也会返回true,但是之后的正则表达式校验无法通过
if (skipped < pattDir.length()) {
if (skipped > 0) {
return true;
}
return (pattDir.length() > 0) && isWildcardChar(pattDir.charAt(0));
}
//skipped ==pattDir长度,全匹配上直接匹配下一个/之后内容
pos += skipped;
}
}
return true;
} //函数作用 待匹配路径字符数组 pos 代表 /所在的下一个位置 prefix标准路径
//标准路径包含wildcardChar { ? * 返回skipped 其他都会返回0
private int skipSegment(char[] chars, int pos, String prefix) {
int skipped = 0;
for (char c : prefix.toCharArray()) {
if (isWildcardChar(c)) {
return skipped;
}
else if (pos + skipped >= chars.length) {
return 0;
}
else if (chars[pos + skipped] == c) {
skipped++;
}
}
return skipped;
}

Spring考虑的很全面,最简单的 /url/{id}   /url/19这种类型匹配完成了;

转换完成以后,存入request域:以HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE作为KEY存储

代码位置:org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch

另外还一种特殊写法:{name:正则表达式校验}  比如我只希望URL中id为纯数字 \d*即可

补充:如果正则匹配不了,抛出的错误是404页面找不到.    带:与不带:的区别在于,不带:就默认使用 .* 匹配,其他用法没差别.

差点忘记记录Spring如何解析@PathVariable注解?

Spring专门的接口HandlerMethodArgumentResolver用来解析方法入参,而PathVariableMethodArgumentResolver就是用来解析 @PathVariable注解的。

而请求参数绑定到 方法入参的方式:

可以看到也是从request属性域HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE中取值,key就是@PathVariable(“yourname”)中的yourname取值.

Spring @Pathvariable的更多相关文章

  1. Restful中 @RequestParam,@PathParam,@PathVariable等注解区别

    @RequestParam 和 @PathVariable 注解是用于从request中接收请求的,两个都可以接收参数,关键点不同的是@RequestParam 是从request里面拿取值,而 @P ...

  2. @RequestParam,@PathParam,@PathVariable,@QueryParam注解的使用区别

    获取url模板上数据的(/{id})@DefaultValue 获取请求参数的(包括post表单提交)键值对(?param1=10&param2=20).可以设置defaultValue JA ...

  3. Spring @RequestParam @RequestBody @PathVariable 等参数绑定注解详解

    背景 昨天一个人瞎倒腾spring boot,然后遇到了一点问题,所以把这个问题总结一下. 主要讲解request 数据到handler method 参数数据的绑定,所用到的注解和什么情形下使用. ...

  4. 转-Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariable

    转-http://snowolf.iteye.com/blog/1628861/ Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariab ...

  5. spring mvc中的@PathVariable(转)

    鸣谢:http://jackyrong.iteye.com/blog/2059307 ------------------------------------------------ spring m ...

  6. Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariable (转)

    最近需要做些接口服务,服务协议定为JSON,为了整合在Spring中,一开始确实费了很大的劲,经朋友提醒才发现,SpringMVC已经强悍到如此地步,佩服! 相关参考: Spring 注解学习手札(一 ...

  7. Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariable(转)

    最近需要做些接口服务,服务协议定为JSON,为了整合在Spring中,一开始确实费了很大的劲,经朋友提醒才发现,SpringMVC已经强悍到如此地步,佩服! 相关参考: Spring 注解学习手札(一 ...

  8. Spring MVC @PathVariable被截断

    一.问题描述 一个控制器提供RESTful访问信息: @RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + &qu ...

  9. spring mvc中的一些注释:@PathVariable @RequestParam等

    请求路径上有个id的变量值,可以通过@PathVariable来获取  @RequestMapping(value = "/page/{id}", method = Request ...

随机推荐

  1. python04 列表 元祖 字典

    1.list 有序,元素可以被修改 li=[1,2,2,"am","123"] 列表中的元素可以是数字,字符串,列表等等 支持切片,切片结果为列表  li[3] ...

  2. js--随机产生100个从0 ~ 1000之间不重复的整数(me)

    <style>       div{text-indent:40px;} </style> <script> window.onload=function(){ v ...

  3. centos6.9 编译安装 zabbix-3.0.15

    本文采用编译安装,请在zabbix官网下载所需版本.此处安装版本是:zabbix-3.0.15.tar.gz 操作系统:centos 6.9 一.安装mysqlmysql-community-libs ...

  4. SpringBoot2.x配置JsonRedisSerializer

    @Configurationpublic class SpringCacheRedisConfig { @Bean public RedisCacheManager cacheManager(Redi ...

  5. 2019.02.11 bzoj1568: [JSOI2008]Blue Mary开公司(线段树)

    传送门 题意简述:维护整体加一条线段,求单点极值. 思路: 直接上李超线段树维护即可. 代码: #include<bits/stdc++.h> #define ri register in ...

  6. (PMP)第13章-----项目相关方管理

    13.1 识别相关方 1 相关方分类的方法: 1.1 权力/利益方格,权力/影响方格,影响/作用方格(小型项目,关系简单) 权力:基于相关方的职权级别: 利益:对项目成果的关心程度 影响:对项目成果的 ...

  7. iOS 系统通知

    iOS相当多一部分系统事件实际上都是通过通知来完成的,依赖于通知的广播形式,我们可以在AppDelegate中通过添加空通知来捕捉所有通知: static NSString *const MYNOTI ...

  8. Batch Normalization--介绍

    思考 YJango的前馈神经网络--代码LV3的数据预处理中提到过:在数据预处理阶段,数据会被标准化(减掉平均值.除以标准差),以降低不同样本间的差异性,使建模变得相对简单. 我们又知道神经网络中的每 ...

  9. docker 安装 RabbitMQ

    1.镜像中国(http://www.docker-cn.com/registry-mirror):直接使用https://hub.docker.com下载镜像比较慢,使用镜像中国加速 使用例子:$ d ...

  10. 菜刀(代码执行)函数和命令执行函数详解及Getshell方法

    i春秋作家:大家奥斯的哦 原文来自:https://bbs.ichunqiu.com/thread-41471-1-1.html 代码执行函数 VS 命令执行函数 一直想整理这两块的内容,但是一直没时 ...