@RequestBody注解的使用及源码解析

前言

@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

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

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

我的环境中存在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

接口响应

相关推荐

  1. SpringBoot

    2024-07-11 11:46:05       55 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 11:46:05       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 11:46:05       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 11:46:05       58 阅读
  4. Python语言-面向对象

    2024-07-11 11:46:05       69 阅读

热门阅读

  1. SLAM中的块矩阵与schur补

    2024-07-11 11:46:05       24 阅读
  2. 第2章 大话 ASP.NET Core 入门

    2024-07-11 11:46:05       24 阅读
  3. git github gitee 三者关系

    2024-07-11 11:46:05       21 阅读
  4. 学习小记-一些Redis小知识

    2024-07-11 11:46:05       23 阅读
  5. 【Spring】springSecurity简介

    2024-07-11 11:46:05       19 阅读
  6. Nginx配置支持WebSocket功能

    2024-07-11 11:46:05       25 阅读
  7. CleanCode、安全编码规范

    2024-07-11 11:46:05       25 阅读