前言
@RequestBody 注解是我们进行JavaEE开发,最常见的几个注解之一,这篇博文我们以案例和源码相结合,帮助大家更好的了解 @RequestBody 注解
使用案例
1.自定义实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
@RestController
@RequestMapping("/request_body")
public class RequestBodyController {
@PostMapping("/entity")
public String entity(@RequestBody User user) {
return user.toString();
}
}
2.使用 Map 接收
@PostMapping("/map")
public String map(@RequestBody Map<String, User> map) {
return map.toString();
}
3.自定义实体类 (复杂对象)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Complex {
private String tag;
private List<User> list;
private User[] array;
private Map<String, User> map;
}
@PostMapping("/complex")
public String complex(@RequestBody Complex complex) {
return complex.toString();
}
请求参数
{
"tag": "complex",
"list": [
{
"username": "anna",
"password": "123"
},
{
"username": "bob",
"password": "456"
}
],
"array": [
{
"username": "cindy",
"password": "135"
},
{
"username": "david",
"password": "246"
}
],
"map": {
"u1": {
"username": "eric",
"password": "159"
},
"u2": {
"username": "frank",
"password": "357"
}
}
}
4.Content-Type 为 application/xml
添加 POM 文件
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.16.1</version>
</dependency>
接口方法 (和案例1只有方法名不一样)
@PostMapping("/xml")
public String xml(@RequestBody User user) {
return user.toString();
}
请求头设置
将 Content-Type 设置为 application/xml (一般情况下 Content-Type 为 application/json)
传递参数
<User>
<username>tom</username>
<password>123</password>
</User>
响应结果
源码解析
InvocableHandlerMethod#getMethodArgumentValues
参数的处理分为两个阶段:
- 判断当前环境中存在的resolvers,是否支持解析当前参数
- 处理参数
判断是否支持解析当前参数
我的环境中存在27个resolvers,通过命名我们大概可以猜测出 RequestResponseBodyMethodProcessor 是处理 @RequestBody 注解的 resolver
RequestResponseBodyMethodProcessor#supportsParameter
即如果参数上存在 @RequestBody 注解,则使用 RequestResponseBodyMethodProcessor 处理参数
RequestResponseBodyMethodProcessor#resolveArgument
RequestResponseBodyMethodProcessor 的 resolveArgument 方法会遍历当前环境中的 HttpMessageConverters,如果存在一个 HttpMessageConverter 的 canRead 方法返回 true,则使用该 HttpMessageConverter 的 read 方法读取数据。
案例1、2、3 的原理其实是一样的,request 的 Content-Type 为 application/json,spring-boot-starter-web 会引入 json 相关依赖,所以默认情况下,SpringMVC 有把 json 对象转换成 key-value 形式数据结构的能力
案例4 我们引用了相关依赖,使得 SpringMVC 有把 xml 对象转换成 key-value 形式数据结构的能力
小结
如果 request 的 Content-Type 为 xxx,并且存在一个 HttpMessageConverter 支持解析 Content-Type 为 xxx 的数据,通过 @RequestBody 注解,就可以将数据绑定到 key-value 形式的数据结构上
扩展:自定义HttpMessageConverter,处理指定 Content-Type 的请求
创建HttpMessageConverter
public class UserTextPlainHttpMessageConverter implements HttpMessageConverter {
@Override
public boolean canRead(Class clazz, MediaType mediaType) {
return clazz == User.class && "text".equals(mediaType.getType()) && "plain".equals(mediaType.getSubtype());
}
@Override
public boolean canWrite(Class clazz, MediaType mediaType) {
return false;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
return Collections.singletonList(MediaType.TEXT_PLAIN);
}
@Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try (InputStream inputStream = inputMessage.getBody();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(sb.toString(), User.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
}
创建配置类
@Configuration
public class MessageConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new UserTextPlainHttpMessageConverter());
}
}
接口方法 (和案例1、4 只有方法名不一样)
@PostMapping("/convert")
public String convert(@RequestBody User user) {
return user.toString();
}
Postman设置
Content-Type 设置为 text/plain
body 传参类型为 text