前言
@RequestParam 注解是我们进行JavaEE开发,最常见的几个注解之一,这篇博文我们以案例和源码相结合,帮助大家更好的了解@RequestParam 注解
使用案例
1.获取 URL 上的值
@GetMapping("/simple")
public String simple(@RequestParam(value = "name") String name) {
return name;
}
![](https://img-blog.csdnimg.cn/direct/a7dd3f404eeb4fa299c752f3e9f24ba7.png)
2.获取 URL 上的值,如果不存在,使用默认值
@GetMapping("/default")
public String defaultValue(@RequestParam(value = "name", defaultValue = "hello world") String name) {
return name;
}
![](https://img-blog.csdnimg.cn/direct/17d1a213dd8a4916a3cfd6a58865e9c7.png)
3.URL 上存在多个指定的 KEY
@GetMapping("/list")
public String list(@RequestParam(value = "name") List<String> names) {
return names.toString();
}
![](https://img-blog.csdnimg.cn/direct/125261777c494d858eb5341eea0e1832.png)
PS : 同样可以使用Set、Collection接收,只要是Collection或其子类都可以
4.使用 Map 接收
4.1 使用接口 Map 接收
@GetMapping("/map")
public String map(@RequestParam Map<String, Object> map) {
return map.toString();
}
![](https://img-blog.csdnimg.cn/direct/61c7185998d24899a98a3fb54a2710f6.png)
4.2 使用 MultiValueMap 接收
@GetMapping("/multi_value")
public String map(@RequestParam MultiValueMap<String, Object> map) {
return map.toString();
}
![](https://img-blog.csdnimg.cn/direct/a34e4063e5234b5b85035591c5f43ec6.png)
5.接收文件
5.1 接收单个文件
@GetMapping("/file")
public String multipart(@RequestParam(value = "file") MultipartFile file) {
return file.getOriginalFilename();
}
![](https://img-blog.csdnimg.cn/direct/94ad16fbe54f4349ac9d9c3811e6285f.png)
5.2 接收多个文件
@GetMapping("/multi_file")
public String multipart(@RequestParam(value = "file") List<MultipartFile> files) {
return files.stream().map(MultipartFile::getOriginalFilename).collect(Collectors.joining(","));
}
![](https://img-blog.csdnimg.cn/direct/2a20379e21cd4150b83eb1075e5447fa.png)
6.其他
6.1 不存在 @RequestParam 注解
@GetMapping("/none")
public String none(String name) {
return StringUtils.isBlank(name) ? "none" : name;
}
![](https://img-blog.csdnimg.cn/direct/5a46d576469040cb88b355eff27cb85b.png)
![](https://img-blog.csdnimg.cn/direct/52ff16399753459aabe73e793336e296.png)
PS : 效果类似上文中的案例2
6.2 Spel表达式
6.2.1 ${}
创建 keys.properties
key=a
引用 keys.properties
@SpringBootApplication
@PropertySource("classpath:keys.properties")
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class);
}
}
接口及响应
@GetMapping("/spel1")
public String spel1(@RequestParam(value = "${key}") String name) {
return name;
}
![](https://img-blog.csdnimg.cn/direct/e75e3bb4da524f9fa3f5bd09368793e6.png)
6.2.1 #{}
创建 RequestKey
@Component
public class RequestKey {
private String key = "b";
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
接口及响应
@GetMapping("/spel2")
public String spel2(@RequestParam(value = "#{requestKey['key']}") String name) {
return name;
}
![](https://img-blog.csdnimg.cn/direct/3b6961b89a254a339c80f3ea5c2ed7a5.png)
源码解析
InvocableHandlerMethod#getMethodArgumentValues
![](https://img-blog.csdnimg.cn/direct/f14c5128f96f4946b0cfc44b1a4e9194.png)
参数的处理分为两个阶段:
- 判断当前环境中存在的resolvers,是否支持解析当前参数
- 处理参数
判断是否支持解析当前参数
![](https://img-blog.csdnimg.cn/direct/ca246edb76954acbb8a7f0fdfeba488b.png)
![](https://img-blog.csdnimg.cn/direct/127c132f07084a2497b3def6f83c0584.png)
我的环境中存在27个resolvers,通过命名我们大概可以猜测出 RequestParamMethodArgumentResolver、RequestParamMapMethodArgumentResolver 是处理 @RequestParam 注解的 resolver
RequestParamMethodArgumentResolver#supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
// 存在RequestParam注解,返回类型是Map,并且指定了name
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
} else {
// 存在@RequestParam注解,并且返回类型不是Map
return true;
}
} else {
// 不存在@RequestPart注解
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
// 返回类型是 MultipartFile 或者 MultipartFile集合、MultipartFile数组
// 返回类型是 Part 或者 Part集合、Part数组
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
// 如果useDefaultResolution属性为true,即使不存在@RequestParam注解,也可以处理普通类
} else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
} else {
return false;
}
}
}
通过上述源码,我们得出以下结论:
- 存在 @RequestParam 注解
- 返回类型是Map
- 指定name:支持
- 未指定name:不支持
- 返回类型非Map:支持
- 返回类型是Map
- 不存在 @RequestParam 注解
- 存在 @RequestPart 注解 :不支持
- 不存在 @RequestPart 注解
- 参数类型是否是MultipartFile(Part):支持
- useDefaultResolution属性是否为true,并且参数类型是普通类 : 支持
- 其他情况 : 不支持
PS : 所以 RequestParamMethodArgumentResolver 也可以处理上文中没有 @RequestParam 注解的情况(案例6.1)。通过下方的截图我们可以发现,存在两个类型都为 RequestParamMethodArgumentResolver 的 resolver ,其中一个的 useDefaultResolution 属性为 true,这个 resolver 就是用来处理没有 @RequestParam 注解,并满足一定条件的传入参数
![](https://img-blog.csdnimg.cn/direct/8034e930d66242c6b15cec2c8cc4eec5.png)
Spring中定义的普通类
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
RequestParamMapMethodArgumentResolver#supportsParameter
@Override
public boolean supportsParameter(MethodParameter parameter) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
!StringUtils.hasText(requestParam.name()));
}
RequestParamMapMethodArgumentResolver 的 supportsParameter 方法比较简单,只能处理满足下面三个条件的参数:
- 存在 @RequestParam 注解
- 返回类型是 Map
- 未指定 value (name)
处理参数
接来下我们将重点分析 RequestParamMethodArgumentResolver 的 resolveArgument 方法,RequestParamMapMethodArgumentResolver 的 resolveArgument 方法大家可以自行阅读,相关源码如下:
![](https://img-blog.csdnimg.cn/direct/0c6a460044c147cbbe2b259571e9e5bb.png)
大概分为以下五个步骤:
- 构建NamedValueInfo对象
- 处理Spel表达式
- 解析参数
- 处理默认值
- 类型转换
构建NamedValueInfo对象
![](https://img-blog.csdnimg.cn/direct/26ea1e369dab4b24ad90d4f7a1e80e42.png)
创建NamedValueInfo对象
如果存在 @RequestParam 注解,则使用自定义的值,否则就使用默认值,通过上述源码,我们可以得知:
- @RequestParam 注解的 required 属性的默认值是 true
- NamedValueInfo 对象的 required 属性的默认值是 false,所以针对案例 6.1,不传相应参数也不会抛出异常
更新NamedValueInfo对象
![](https://img-blog.csdnimg.cn/direct/e2d8f3ffa78241cbb1261e81896bc62f.png)
updateNamedValueInfo 方法主要针对不存在@RequestParam 注解,NamedValueInfo对象的 name 属性值为方法的参数名
处理Spel表达式
![](https://img-blog.csdnimg.cn/direct/bf4bee3e97384e7c80e016c3f31c3d26.png)
主要对Spel表达式进行解析,比如案例 6.2.1 中 ${key} 会被解析成 a ,案例 6.2.2 中 #{requestKey['key']} 会被解析成 b
解析参数
RequestParamMethodArgumentResolver#resolveName
![](https://img-blog.csdnimg.cn/direct/3444502ec1034c13ac035c5dcae96f71.png)
总体分为三个优先级:
- HttpServletRequest 类型为 MultipartHttpServletRequest 或 contentType 以 multipart/ 开头,则处理 MultipartFile (Part)类型的传参
- HttpServletRequest 类型为 MultipartRequest 则处理 MultipartFile 类型的参数
- 将 URL 或 body 中的传参,以 String (String[])返回 (如果存在相应的 convert,则进行类型转换)
PS : 默认情况下,如果 request 的 contentType 以 multipart/ 开头,SpringBoot 会将请求封装成 StandardMultipartHttpServletRequest,它是 MultipartHttpServletRequest 的子类
![](https://img-blog.csdnimg.cn/direct/ce8b8fb8c6e84475b54b8f61d9fc79d7.png)
![](https://img-blog.csdnimg.cn/direct/0d3c59e83bf74d1386fca6ce2391a204.png)
![](https://img-blog.csdnimg.cn/direct/dd45a1aea1934aa5b7f7f7cd69f5b531.png)
![](https://img-blog.csdnimg.cn/direct/d9c018b7dd3d4d1ab60b415842ad8da3.png)
处理默认值
处理默认值的两种情况
- 参数解析结果为 null,@RequestParam 注解的 required 属性为 false,并且设置了默认值
- 参数解析结果为空字符串,并且设置了默认值 (尝试以Spel表达式的方式进行解析)
类型转换
![](https://img-blog.csdnimg.cn/direct/6c999ccad1404b55aca3619878ee0b09.png)
SpringBoot 会提前内置很多 convert,当存在一个 convert 可以将当前类型转换为目标类型,则会进行转换。比如案例3中,需要一个将 String 数组转换为 LIst 的 convert,因为该 convert (上图框中的 convert)存在,所以我们可以用 List (当前类型和目标类型与 convert 类型一致或是其子类都可以)去接收参数。
自定义Convert
除了系统内置的 convert,我们也可以自定义 convert,案例演示如下:
创建配置类 WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Dog>() {
@Override
public <U> Converter<String, U> andThen(Converter<? super Dog, ? extends U> after) {
return Converter.super.andThen(after);
}
@Override
public Dog convert(String source) {
return new Dog(source);
}
});
}
}
创建实体类 Dog
public class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
接口及响应
@GetMapping("/convert")
public String convert(@RequestParam(value = "name") Dog dog) {
return dog.toString();
}
![](https://img-blog.csdnimg.cn/direct/6a1f964780b9409c8b2e8d4c6c7b0e76.png)