@PathVariable注解的使用及源码解析
前言
@PathVariable 注解是我们进行JavaEE开发,最常见的几个注解之一,这篇博文我们以案例和源码相结合,帮助大家更好的了解@PathVariable 注解
使用案例
1.获取 URL 上的值
@RequestMapping("/id/{id}")
public Object getId(@PathVariable(value = "id") Integer id) {return id;
}
2.获取 URL 上的多个值
多个值以 / 分割,可以相邻也可以不相邻
2.1 多个值相邻
@GetMapping("/info/{id}/{name}")
public Object getInfo(@PathVariable(value = "id") Integer id, @PathVariable(value = "name") String name) {return "id:" + id + ",name:" + name;
}
2.1 多个值不相邻
@GetMapping("/info/{id}/delimiter/{name}")
public Object getDelimiterInfo(@PathVariable(value = "id") Integer id, @PathVariable(value = "name") String name) {return "id:" + id + ",name:" + name;
}
3. 不指定value(name)
@RequestMapping("/default/{id}")
public Object getDefaultId(@PathVariable Integer id) {return id;
}
4. 使用 Map 接受多个 URL 上的值
@GetMapping("/info_map/{id}/{name}")
public Object getInfoToMap(@PathVariable Map<String, Object> map) {StringBuilder sb = new StringBuilder();map.forEach((key, value) -> sb.append(key).append(":").append(value).append(","));return sb.substring(0, sb.length() - 1);
}
源码解析
InvocableHandlerMethod#getMethodArgumentValues
参数的处理分为两个阶段:
- 判断当前环境中存在的resolvers,是否支持解析当前参数
- 处理参数
判断是否支持解析当前参数
我的环境中存在27个resolvers,通过命名我们大概可以猜测出 PathVariableMethodArgumentResolver、PathVariableMapMethodArgumentResolver 是处理 @PathVariable 注解的 resolver
PathVariableMethodArgumentResolver#supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {if (!parameter.hasParameterAnnotation(PathVariable.class)) {return false;}if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);return (pathVariable != null && StringUtils.hasText(pathVariable.value()));}return true;
}
如果参数存在 @PathVariable 注解,并且指定了 value(name),则支持解析
PathVariableMapMethodArgumentResolver#supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&!StringUtils.hasText(ann.value()));
}
如果参数存在 @PathVariable 注解,并且未指定了 value(name),则支持解析
处理参数
接来下我们将重点分析 PathVariableMethodArgumentResolver 的 resolveArgument 方法,PathVariableMapMethodArgumentResolver 的 resolveArgument 方法大家可以自行阅读,相关源码如下:
大概分为以下六个步骤:
- 构建NamedValueInfo对象
- 处理Spel表达式
- 解析参数
- 处理默认值
- 类型转换
- 给 Request 域赋值
构建NamedValueInfo对象
创建NamedValueInfo对象
@PathVariable 注解的默认值是 ValueConstants.DEFAULT_NONE,并且无法手动设置
更新NamedValueInfo对象
updateNamedValueInfo 方法主要针对 @PathVariable 注解未指定 value(name)的情况,比如上文中的案例3,NamedValueInfo对象的 name 属性值为方法的参数名
处理Spel表达式
默认情况下,@PathVariable 注解是不支持解析 Spel 表达式的,我们通过源码分析一下。
如果 resolver 的 configurableBeanFactory 或 expressionContext 属性为 null ,则不进行Spel表达式的解析工作
RequestMappingHandlerAdapter#getDefaultArgumentResolvers
默认注册 PathVariableMethodArgumentResolver 使用的是无参构造方法,也就是 configurableBeanFactory 和 expressionContext 属性为 null,所以默认情况下,@PathVariable 注解不支持解析Spel表达式。可能有的小伙伴说我可以利用 WebMvcConfigurer ,自定义一个resolver,这里存在一个优先级问题,自定义的 resolver 优先级低于 mvc 手动注册的 resolver,所以一般情况下 @PathVariable 注解都不支持解析Spel表达式
我们可以看到手动注册的 resolver 优先级很低,一般情况下都是利用 mvc 内置的 resolver 进行解析
如何让 @PathVariable 注解支持解析 Spel 表达式 ?
替换内置的 PathVariableMapMethodArgumentResolver
@SpringBootApplication
@PropertySource("classpath:keys.properties")
public class BootApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(BootApplication.class);ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class);List<HandlerMethodArgumentResolver> resolvers = adapter.getArgumentResolvers();if (!CollectionUtils.isEmpty(resolvers)) {try {List<HandlerMethodArgumentResolver> newResolvers = new ArrayList<>();for (HandlerMethodArgumentResolver resolver : resolvers) {if (resolver instanceof PathVariableMethodArgumentResolver) {PathVariableMethodArgumentResolver pathVariableMethodArgumentResolver = new PathVariableMethodArgumentResolver();Field factoryField = AbstractNamedValueMethodArgumentResolver.class.getDeclaredField("configurableBeanFactory");factoryField.setAccessible(true);factoryField.set(pathVariableMethodArgumentResolver, beanFactory);Field expressionField = AbstractNamedValueMethodArgumentResolver.class.getDeclaredField("expressionContext");expressionField.setAccessible(true);expressionField.set(pathVariableMethodArgumentResolver, new BeanExpressionContext(beanFactory, new RequestScope()));newResolvers.add(pathVariableMethodArgumentResolver);} else {newResolvers.add(resolver);}}adapter.setArgumentResolvers(Collections.unmodifiableList(newResolvers));} catch (Exception ignore) {}}}
}
这里我们引用了一个 keys.properties 文件,为下文中演示解析Spel表达式做准备,文件明细如下
key=a
解析 ${}
@RequestMapping("/spel_1/{a}/{b}")
public Object spel1(@PathVariable(value = "${key}") String key) {return key;
}
解析 #{}
创建 RequestKey
@Component
public class RequestKey {private String key = "b";public String getKey() {return key;}public void setKey(String key) {this.key = key;}
}
接口及响应
@RequestMapping("/spel_2/{a}/{b}")
public Object spel2(@PathVariable(value = "#{requestKey['key']}") String key) {return key;
}
参数解析
主要就是获取 URL 上的值
处理默认值
@PathVariable 注解不支持设置默认值,源码这里又处理默认值,感觉有点突兀。其实这里使用了模板模式,@RequestParam 注解的处理步骤是和 @PathVariable 注解一致的,然而 @RequestParam 注解是可以设置默认值的。我的上一篇博文写了@RequestParam 注解的使用和源码解析,有兴趣的小伙伴可以阅读一下,它们的处理流程基本是一致的,就是细节有差别。博文链接 : @RequestParam注解的使用及源码解析
类型转换
SpringBoot 会提前内置很多 convert,当存在一个 convert 可以将当前类型转换为目标类型,则会进行转换。案例演示:
创建实体类 Dog
public class Dog {private String name;public Dog(String name) {this.name = name;}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +'}';}
}
创建配置类 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Dog>() {@Overridepublic <U> Converter<String, U> andThen(Converter<? super Dog, ? extends U> after) {return Converter.super.andThen(after);}@Overridepublic Dog convert(String source) {return new Dog(source);}});}}
接口及响应
@GetMapping("/convert/{dog}")
public String getDog(@PathVariable(value = "dog") Dog dog) {return dog.toString();
}
给 Request 域赋值
案例演示
@GetMapping("/attribute/{id}")
public String getAttribute(@PathVariable(value = "id") Integer id, HttpServletRequest request) {return request.getAttribute(View.PATH_VARIABLES).toString();
}
相关文章:

@PathVariable注解的使用及源码解析
前言 PathVariable 注解是我们进行JavaEE开发,最常见的几个注解之一,这篇博文我们以案例和源码相结合,帮助大家更好的了解PathVariable 注解 使用案例 1.获取 URL 上的值 RequestMapping("/id/{id}") public Object getId(Path…...
服务器配置重点看哪些参数
对服务器有需求时,应重点考虑以下几个关键参数,以下仅供参考: 处理器(CPU):包括CPU的品牌(如Intel或AMD)、型号、核心数、线程数、主频和缓存大小。核心数越多,处理并发请…...
WSL Ubuntu 如何设置中文语言?
本章教程,主要介绍如何在WSL Ubuntu 如何设置中文语言。 操作系统:Windows 10 Pro 64 WSL子系统:Ubuntu 20.04 LTS 一、安装中文语言包 sudo apt install language-pack-zh-hans二、设置中文语言 sudo dpkg-reconfigure locales选择en_US.UTF-8 和 zh_CN.UTF-8 选择zh_CN.…...

「51媒体」政企活动媒体宣发如何做?
传媒如春雨,润物细无声,大家好,我是51媒体网胡老师。 媒体宣传加速季,100万补贴享不停,一手媒体资源,全国100城线下落地执行。详情请联系胡老师。 政企活动媒体宣发是一个系统性的过程,需要明确…...
K近邻回归原理详解及Python代码示例
K近邻回归原理详解 K近邻回归(K-Nearest Neighbors Regression, KNN)是一种基于实例的学习算法,用于解决回归问题。它通过找到输入数据点在特征空间中最相似的K个邻居(即最近的K个数据点),并使用这些邻居的…...

idea 开发工具properties文件中的中文不显示
用idea打开一个项目,配置文件propertise中的中文都不展示,如图: 可修改idea配置让中文显示: 勾选箭头指向的框即可,点击应用保存,重新打开配置文件,显示正常...

让DroidVNC-NG支持中文输入
DroidVNC-NG支持控制端输入内容,但是仅支持英文字符,如果需要控制输入法软键盘输入中文的话就没办法了,经过摸索找到了解决办法。 这个解决办法有个条件就是让DroidVNC-NG成为系统级应用(这个条件比较苛刻)ÿ…...
android dialog 显示时 activity 是否会执行 onPause onStop
当一个 Android Dialog 显示时,当前 Activity 通常不会执行 onPause 或 onStop 方法。Dialog 是附加到 Activity 上的一个窗口,它不会中断或替换当前的 Activity,因此 Activity 的生命周期方法 onPause 和 onStop 不会被调用。 然而…...

如何在MySQL中按字符串中的数字排序
在管理数据库时,我们经常遇到需要按嵌入在字符串中的数字进行排序的情况。这在实际应用中尤为常见,比如文件名、代码版本号等字段中通常包含数字,而这些数字往往是排序的关键。本文将详细介绍如何在MySQL中利用正则表达式提取字符串中的数字并…...

memcacheredis构建缓存服务器
Memcached&Redis构建缓存服务器 前言 许多Web应用都将数据保存到 RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、 网站显示延迟等重大影响。Memcached/redis是高性能…...

Linux基础- 使用 Apache 服务部署静态网站
目录 零. 简介 一. linux安装Apache 二. 创建网页 三. window访问 修改了一下默认端口 到 8080 零. 简介 Apache 是世界使用排名第一的 Web 服务器软件。 它具有以下一些显著特点和优势: 开源免费:可以免费使用和修改,拥有庞大的社区支…...

接口自动化测试框架实战(Pytest+Allure+Excel)
🍅 视频学习:文末有免费的配套视频可观看 🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 1. Allure 简介 Allure 框架是一个灵活的、轻量级的、支持多语言的测试报告工具,它不…...

如何预防和处理他人盗用IP地址?
IP地址的定义及作用 解释 IP 地址在互联网中的作用。它是唯一标识网络设备的数字地址,类似于物理世界中的邮政地址。 1、IP地址盗窃的定义 解释一下什么是IP地址盗用,即非法使用他人的IP地址或者伪造IP地址的行为,这种行为可能引发法律和安…...

【ai】李沐 动手深度学学v2 环境安装:anaconda3、pycharm、d2
cuda-toolkit cuda_12.5.0_windows_network.exe 官方课程网站 第二版资源下载release版本 pycharm版本 李沐 【动手学深度学习v2 PyTorch版】 课程笔记 CUDA 选择11, 实际下载 12.5.0...
前后端分离对软件行业及架构设计的影响
在软件开发领域,前后端分离是一种越来越流行的架构设计模式。这种方法将用户界面(前端)与服务器逻辑(后端)分离开来,允许它们独立开发、测试和部署。本文将探讨前后端分离对软件行业和架构设计的影响&#…...
深入解析Dubbo架构层次
什么是Dubbo? Dubbo是阿里巴巴开源的一款高性能优秀的服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。它的主要功能包括: 远程通信:提供高效的远程通信能力。负载均衡࿱…...
关于GPIO的上拉、下拉,无上下拉
1.GPIO_PULLUP(上拉) 作用和原理 作用:上拉模式会在GPIO引脚和电源电压(Vcc)之间连接一个内部上拉电阻。原理:当引脚配置为输入模式时,如果引脚没有连接到其他外部电路,内部上拉电…...

Python 语法基础二
7.常用内置函数 执行这个命令可以查看所有内置函数和内置对象(两个下划线) >>>dir(__builtins__) [__class__, __contains__, __delattr__, __delitem__, __dir__, __doc__, __eq__, __format__, __ge__, __getattribute__, __getitem__, __gt…...
HTML5与HTML:不仅仅是标签的革新
当我们提到HTML5,很多人会想到这是HTML的一个升级版本,增加了许多新的标签和特性。然而,HTML5带来的变化远不止于此。它是一个全面的网页开发框架,重新定义了网络应用程序的构建方式,为开发者提供了前所未有的灵活性和…...
Mybatis面试学习
1.介绍一下mybatis mybatis是一个半自动的ORM的框架,ORM就是对象关系映射。(对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应关系)…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...