当前位置: 首页 > news >正文

@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

参数的处理分为两个阶段:

  1. 判断当前环境中存在的resolvers,是否支持解析当前参数
  2. 处理参数

判断是否支持解析当前参数

我的环境中存在27个resolvers,通过命名我们大概可以猜测出 PathVariableMethodArgumentResolverPathVariableMapMethodArgumentResolver 是处理 @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 方法大家可以自行阅读,相关源码如下:

大概分为以下六个步骤:

  1. 构建NamedValueInfo对象
  2. 处理Spel表达式
  3. 解析参数
  4. 处理默认值
  5. 类型转换
  6. 给 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();
}

http://www.lryc.cn/news/385497.html

相关文章:

  • 服务器配置重点看哪些参数
  • WSL Ubuntu 如何设置中文语言?
  • 「51媒体」政企活动媒体宣发如何做?
  • K近邻回归原理详解及Python代码示例
  • idea 开发工具properties文件中的中文不显示
  • 让DroidVNC-NG支持中文输入
  • android dialog 显示时 activity 是否会执行 onPause onStop
  • 如何在MySQL中按字符串中的数字排序
  • memcacheredis构建缓存服务器
  • Linux基础- 使用 Apache 服务部署静态网站
  • 接口自动化测试框架实战(Pytest+Allure+Excel)
  • 如何预防和处理他人盗用IP地址?
  • 【ai】李沐 动手深度学学v2 环境安装:anaconda3、pycharm、d2
  • 前后端分离对软件行业及架构设计的影响
  • 深入解析Dubbo架构层次
  • 关于GPIO的上拉、下拉,无上下拉
  • Python 语法基础二
  • HTML5与HTML:不仅仅是标签的革新
  • Mybatis面试学习
  • el-date-picker设置时间范围
  • Links: Challenging Puzzle Game Template(益智游戏模板)
  • java基于ssm+jsp 仓库智能仓储系统
  • 第24篇 滑动开关控制LED<二>
  • Redis单例部署
  • HarmonyOS4升级到Harmonyos Next(Api 11)学习捷径
  • [电子电路学]电路分析基本概念1
  • Linux bash: /usr/local/gcc/bin/gcc: 不是目录
  • vue项目中,pnpm不能用-解决
  • 数据处理python
  • 【MotionCap】SLAHMR 在 Colab 的demo运行笔记