前言
@RequestBody 注解是我们进行JavaEE开发,最常见的几个注解之一,这篇博文我们以案例和源码相结合,帮助大家更好的了解 @RequestBody 注解
使用案例
1.自定义实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private String username;private String password;@Overridepublic 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 {@Overridepublic boolean canRead(Class clazz, MediaType mediaType) {return clazz == User.class && "text".equals(mediaType.getType()) && "plain".equals(mediaType.getSubtype());}@Overridepublic boolean canWrite(Class clazz, MediaType mediaType) {return false;}@Overridepublic List<MediaType> getSupportedMediaTypes() {return Collections.singletonList(MediaType.TEXT_PLAIN);}@Overridepublic 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);}}@Overridepublic void write(Object o, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {}
}
创建配置类
@Configuration
public class MessageConfig implements WebMvcConfigurer {@Overridepublic void configureMessageConverters(List<HttpMessageConverter<?>> converters) {converters.add(new UserTextPlainHttpMessageConverter());}}
接口方法 (和案例1、4 只有方法名不一样)
@PostMapping("/convert")
public String convert(@RequestBody User user) {return user.toString();
}