前言
@RequestPart 注解是我们在JavaEE 开发中,比较常见的一个注解。它经常会与 @RequestBody 、@RequestParam 注解进行比较,这篇博文我们以案例和源码相结合,分析这几个注解的异同点。
案例演示
创建实体类 User
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
@RequestPart vs @RequestBody
实体类User接收传参
@RequestBody 方式接口及响应
@RestController
@RequestMapping("/compare")
public class CompareController {
@PostMapping("/body_entity")
public String bodyEntity(@RequestBody User user) {
return user.toString();
}
}
@RequestPart 方式接口及响应
@PostMapping("/part_entity")
public String partEntity(@RequestPart("user") User user) {
return user.toString();
}
使用 @RequestBody 方式传递数据会报错
接收文件
@RequestPart 方式接口及响应
@PostMapping("/part_file")
public String partFile(@RequestPart("file") MultipartFile file) {
return file.getOriginalFilename();
}
PS : 一般情况 @RequestBody 注解不支持接收 MultipartFile 类型的数据
异同点
- 相同点
- 都可以用实体类接收传参
- 不同点
- @RequestPart 仅支持 Content-Type 以 multipart/ (multipart/form-data | multipart/mixed) 开头的 request
- @RequestPart 可以接收文件类型
@RequestPart vs @RequestParam
接收文件
@RequestPart 方式已经在上文中演示了,这里主要演示 @RequestParam 方式
@RequestParam 方式接口及响应
@PostMapping("/param_file")
public String paramFile(@RequestParam("file") MultipartFile file) {
return file.getOriginalFilename();
}
实体类User接收传参
@RequestPart 方式已经在上文中演示了,这里主要演示 @RequestParam 方式
创建UserConfig,添加自定义 convert
@Configuration
public class UserConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, User>() {
@Override
public <U> Converter<String, U> andThen(Converter<? super User, ? extends U> after) {
return Converter.super.andThen(after);
}
@Override
public User convert(String source) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(source, User.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
}
}
@RequestParam 方式接口及响应
@PostMapping("/param_entity")
public String paramEntity(@RequestParam("user") User user) {
return user.toString();
}
PS : 默认情况下, @RequestParam 都以 String 接收传参(非 MultipartFile | Part 类型),然后再遍历当前环境中的 converts,依次尝试将传参转换成指定类型
异同点
- 相同点
- 都可以用实体类接收传参
- 都可以接收文件类型
- 不同点
- @RequestPart 依靠 HttpMessageConverter 接收实体类,@RequestParam 依靠 Converter 接收实体类
- @RequestParam 可以有默认值
- @RequestParam 可以解析 URL 上的传参
- 。。。
PS : @RequestParam 功能还是比较强大的,我会在下文贴上博文地址,不同点还有挺多的
源码解析
@RequestPart、@RequestBody、@RequestParam 的源码解析流程大同小异,在之前我已经写了 关于 @RequestParam 、@RequestBody 注解的两篇博文,如果搞懂这两个注解,@RequestPart 注解自然也就懂了。根据我的理解,大概有这样一个公式 @RequestPart = @RequestBody + @RequestParam处理文件类型部分
相关博文链接
- @RequestBody: @RequestBody注解的使用及源码解析
- @RequestParam:@RequestParam注解的使用及源码解析
InvocableHandlerMethod#getMethodArgumentValues
参数的处理分为两个阶段:
- 判断当前环境中存在的 resolvers,是否支持解析当前参数
- 处理参数
判断是否支持解析当前参数
我的环境中存在27个resolvers,通过命名我们大概可以猜测出 RequestPartMethodArgumentResolver 是处理 @RequestPart 注解的 resolver
RequestPartMethodArgumentResolver#supportsParameter
RequestPartMethodArgumentResolver 可以处理两种情况的传参:
- 存在 @RequestPart 注解
- 不存在 @RequestParam 注解,且参数类型是 MultipartFile | Part (或其集合形式)
RequestPartMethodArgumentResolver#resolveArgument
通过源码我们可以得出以下结论:
参数类型是 MultipartFile | Part :使用 MultipartResolutionDelegate#resolveMultipartArgument 方法进行处理
其他类型:使用 AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters 方法进行处理
@RequestPart vs @RequestParam
@RequestParam 基本上是由 RequestParamMethodArgumentResolver 这个类来处理的,我们来看相关源码
RequestParamMethodArgumentResolver#resolveArgument
如果参数类型是 MultipartFile | Part,它们都是通过同一个方法处理的
@RequestPart vs @RequestBody
@RequestBody 基本上是由 RequestResponseBodyMethodProcessor 这个类来处理的,我们来看相关源码
RequestResponseBodyMethodProcessor#resolveArgument
都是调用 readWithMessageConverters 方法进行处理