当前位置: 代码迷 >> 综合 >> 【SpringBoot】由一个 @RequestBody 注解引起了我的深思
  详细解决方案

【SpringBoot】由一个 @RequestBody 注解引起了我的深思

热度:78   发布时间:2024-01-10 14:11:23.0

1. HTTP 协议

?HTTP 协议 是一个应用层协议,它是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

?HTTP 协议 是一个无状态的请求/响应协议。


请求消息对象 Request

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:

  • 请求行
  • 请求头部
  • 空行
  • 请求数据(请求体 body)

如下图:
在这里插入图片描述
【注意】:
        请求体 body 可以为空,常见的 GET 请求就是这种情况。但当请求体 body 不为空时,接收的一端需要知道它是什么类型的数据,采用什么编码。这时候,就需要在 Content-Type 来指明请求体 body 的媒体格式(MIME)类型。

        GET 请求一般(标准)不包含请求体 body,不用设置 Content-Type,但 POST 请求一般(标准)包含请求体 body,需要设置 Content-Type

GET 请求:

GET /user?id=1 HTTP/1.1
Host    img.mukewang.com
User-Agent    Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept    image/webp,image/*,*/*;q=0.8
Referer    http://www.imooc.com/
Accept-Encoding    gzip, deflate, sdch
Accept-Language    zh-CN,zh;q=0.8
  1. 第一部分:请求行,用来说明请求类型、要访问的资源、所使用的HTTP版本。
    GET说明请求类型为 GETuser?id=1 为要访问的资源,该行的最后一部分说明使用的是 HTTP1.1 版本。

  2. 第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
    从第二行起为请求头部,HOST 将指出请求的目的地;User-Agent,服务器端和客户端脚本都能访问它。它是浏览器类型检测逻辑的重要基础。该信息由你的浏览器来定义,并且在每个请求中自动发送等等

  3. 第三部分:空行,请求头部后面的空行是必须的
    即使第四部分的请求数据为空,也必须有空行。

  4. 第四部分:请求数据也叫主体,可以添加任意的其他数据。一般为空

POST请求:

POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alivename=zzc&age=24
  1. 第一部分:请求行,第一行明了是post请求,以及http1.1版本。
  2. 第二部分:请求头部,第二行至第六行。
  3. 第三部分:空行,第七行的空行。
  4. 第四部分:请求数据name=zzc&age=24

GET 和 POST 提交方式的区别:

  1. GET 提交,请求的数据会附在URL之后(把数据放置在请求行(request line)中),以 ? 分割 URL和传输数据,多个参数用 & 连接;url 的编码格式采用的是 ASCII 码,而不是 Unicode,这也就是说你不能在 url 中包含任何非 ASCII 字符,所有非 ASCII 字符均需要编码再传输。关于 url 编码可参考:URL编码与解码
  2. POST 提交:把提交的数据放置在是 HTTP 包的包体中。即:请求体 body 中。

响应消息 Response

一般情况下,服务器接收并处理客户端发过来的请求后会返回一个 HTTP 的响应消息。

HTTP响应也由四个部分组成,分别是:

  • 状态行
  • 消息报头
  • 空行
  • 响应正文

如下图:
在这里插入图片描述

HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8<html><head></head><body><!--body goes here--></body>
</html>
  1. 第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
    第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)

  2. 第二部分:消息报头,用来说明客户端要使用的一些附加信息
    第二行和第三行为消息报头,
    Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8

  3. 第三部分:空行,消息报头后面的空行是必须的

  4. 第四部分:响应正文,服务器返回给客户端的文本信息。
    空行后面的html部分为响应正文。

注意:在响应消息中,也返回了 Content-Type 字段。它用来告诉客户端实际返回的内容的内容类型

2. Content-Type

Content-Type 是 HTTP 的实体首部字段,在 request 的请求行或 response 的状态之后(请求头和响应头中都存在),也是首部的一部分,用于说明请求或返回的消息主体是用何种方式编码。

Content-Type 的常用类型:

Content-Type 的常用类型:

  1. application/x-www-form-urlencoded表单 Form 提交(encType=application/x-www-form-urlencoded)
  2. multipart/form-data表单 Form 提交(encType=multipart/form-data)
  3. application/jsonJSON 提交

Ps:在 Form 元素的语法中,encType 表明提交数据的格式:指定将数据回发到服务器时浏览器使用的编码类型。
application/x-www-form-urlencoded :窗体数据被编码为名称/值对。这是标准的编码格式
multipart/form-data:窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分
text/plain: 窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符

1. application/x-www-form-urlencoded:

application/x-www-form-urlencoded 是浏览器表单 POST 提交的默认方式。

  • POST 提交:把 form 中的数据封装到 HTTP 的请求体中,然后,发送到服务器
  • GET 提交:提交的数据按照 key1=value1&key2=value2 的方式进行编码(标准的编码格式)。其中, key 和 value 都进行了 URL 转码。就是 URL 里面的 QueryString

在服务器端可直接使用 request.getParamater() 方法获取参数(request.getInputStream() 或 request.getReader() 可获取到请求内容,再解析出具体的参数)。

如果没有 type=file 的控件,用默认的 application/x-www-form-urlencoded 就可以了。 但是如果有 type=file 的话,就要用到multipart/form-data 了。浏览器会把整个表单以控件为单位分割,并为每个部分加上 Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)

2. multipart/form-data:

multipart/form-data:是常见的 POST 数据提交的方式;支持向服务器发送二进制数据;多用于 文件上传

使用表单上传文件时,必须让 form 的 enctype 等于这个值,表单数据都保存在 HTTP 的请求体 body 部分,各个表单项之间用 boundary 分开,即:

<form action="/" method="post" enctype="multipart/form-data"><input type="text" name="description" value="some text"><input type="file" name="myFile"><button type="submit">Submit</button>
</form>

传输的数据为:

POST /foo HTTP/1.1Content-Length: 68137
Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575---------------------------974767299852498929531610575
Content-Disposition: form-data; name="description"
Content-Type: 
some text
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="myFile"; filename="foo.txt"
Content-Type: text/plain
(content of the uploaded file foo.txt)
--974767299852498929531610575--

首先生成了一个 boundary(很长很复杂,为了避免与正文内容重复。服务器会根据这个边界解析数据,划分段,每一段就是一项数据)用于分割不同的字段 。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。

消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。

如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。

通过 request.getInputStream() 方法获取数据(request.getParamater() 方法是无法获取数据的),但它获取的是一个 InputStream,无法直接取到指定的表单项。但可直接利用开源组件获取表单项。

如: Apache 的 fileupload 组件。

ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> list = upload.parseRequest(request);
// 遍历 list 来获取参数

使用 SpringBoot 上传文件:

public class User {
    private String username;private String password;private String touXiang;
}

实体类中的属性 touXiang 对应着下面的 html 页面的 file 类型。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Index</title>
</head>
<body><h1>Hello World</h1><form action="/user/login" method="post" enctype="multipart/form-data"><p>账号:<input type="text" name="username" /></p><p>密码:<input type="password" name="password" /></p><p>头像:<input type="file" name="file" /></p><input type="submit" value="登陆" /></form>
</body>
</html>

上述的 typen = “file” 的输入框的 name 的值(file) 不要和实体类中的属性名 touXiang 一致。

@RestController
@RequestMapping("/user")
public class UserController {
    private static final String FILEPATH = "E:/sql/";@RequestMapping("/login")public User login(String username, String password, MultipartFile file) {
    String touXiang = FILEPATH + file.getOriginalFilename();User user = new User();user.setUsername(username);user.setPassword(password);user.setTouXiang(touXiang);try {
    file.transferTo(new File(touXiang));} catch (IOException e) {
    e.printStackTrace();}return user;}
}

入参类型为 MultipartFile 的参数名(file)要和前端的 name 的值一致。

3.application/json:

application/json:它是告诉服务器请求体是序列化后的 JSON 字符串;

{
    "name": "zzc","age": 24
}

Spring 对它上传的数据有很好的支持,可以直接通过 @RequestBody 进行接收;

application/x-www-form-urlencoded 与 application/json 的区别:

区别一:

application/x-www-form-urlencoded:浏览器表单提交的默认方式;JQuery 的 AJAX 请求的默认方式。键值对形式:key1=value1&key2=value2

application/json:JSON 字符串格式

区别二:

application/x-www-form-urlencoded:对象接收、@RequestParam注解 接收

application/json(axios默认使用):只能以 @ResquestBody注解(只能接收单个参数) 接收对象

3. @RequestBody、@RequestParam注解

3.1 使用 @RequestBody、@RequestParam注解

@RequestBody、@RequestParam注解都作用于 Controller 层的接口,用来获取参数。

  • @RequestParam:获取参数。请求方式:GET、POST
  • @RequestBody:将 JSON 格式的字符串转换为 JAVA 对象。请求方式:POST

@RequestBody 注解是用来接收请求体的参数,由于 GET 请求的参数是拼接在 url 后面(且是键值对形式,不是 json字符串),位于请求头中,并非请求体。故,无法接受 GET 请求的参数。而 POST 请求的参数正是位于请求体中。故,能接收 POST 请求的参数。

@RequestParam 注解

@RequestParam 的原理是 Spring 将 request.getParameter() 中 的 Key-Value 参数 Map 转化成了参数`@RequestParam 修饰的基本类型数据或对象。

@RequestParam 注解能接收 Content-Type=application/x-www-form-urlencoded 的 GET、POST请求方式;超链接(GET 请求)

表单的 GET/POST 请求:Content-Type=application/x-www-form-urlencoded

页面传参:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Index</title>
</head>
<body><!--默认表单的Content-Type=application/x-www-form-urlencoded--><form action="/user/login" method="get"><p>账号:<input type="text" name="username" /></p><p>密码:<input type="password" name="password" /></p><input type="submit" value="登录" /></form>
</body>
</html>

POSTMAN 模仿表单传参:
在这里插入图片描述
POSTMAN 模仿超链接传参:
在这里插入图片描述
后台接收:

@RestController
@RequestMapping("/param")
public class ParamController {
    // 1. 直接使用参数接收@GetMapping("/login")public String login(String name, String password) {
    return name + password;}// 2. 使用对象接收@GetMapping("/login2")public UserVo login2(UserVo userVo) {
    return userVo;}// 3. 使用 @RequestParam 注解接收。其中,@RequestParam(val) 中的 val 是与前端传来的参数名一致(name对应的值)。// 如:将 name="password" 改为 name="pwd"@GetMapping("/login3")public String login3(@RequestParam("name") String name, @RequestParam("password") String pwd) {
    return name + pwd;}}
@Data
public class UserVo {
    private String name;private String password;private Integer age;
}

@RequestParam注解 接收 application/json 格式编码的参数会报错。

POSTMAN 集合 传参:
在这里插入图片描述
或者:
在这里插入图片描述
后台代码(GET/POST 方法都可以):

@GetMapping("/getList")
public List<Integer> getList(@RequestParam("ids") List<Integer> ids) {
    return ids;
}

POSTMAN 中的:Content-Type=application/x-www-form-urlencoded
在这里插入图片描述
只能是 POST 方法

@PostMapping("/getList2")
public String getList2(HttpServletRequest request) {
    System.out.println(JSON.toJSONString(request.getParameterMap()));return request.getParameter("id");
}@PostMapping("/getList3")
public String getList3(User user) {
    return user.getId();
}@PostMapping("/getList5")
public String getList5(@RequestParam Map<String, String> map) {
    return map.get("id");
}@PostMapping("/getList6")
public String getList6(@RequestParam String id) {
    return id;
}@PostMapping("/getList7")
public String getList7(String id) {
    return id;
}

@RequestBody 注解

只接收 application/json 格式的参数。

使用 POSTMAN 进行 POST 请求

在这里插入图片描述
在这里插入图片描述
后台接收:

@PostMapping("/login4")
public UserVo login4(@RequestBody UserVo userVo) {
    return userVo;
}

接口集合:
在这里插入图片描述
后台代码:

@PostMapping("/postListJson")
public List<Integer> postListJson(@RequestBody List<Integer> ids) {
    return ids;
}

3.2 Controller 层的方法的多个入参无法使用多个 @RequestBody注解

? @RequestBody注解是在当前的对象中只获取一次整个 HTTP 请求的 body 里面的所有数据,因此, Spring 就不可能将这个数据强制包装成 A 参数和 B参数,也就没必要在 Controller 层的方法的形参列表中出现多个 @RequestBody 注解。

public class Address {
    private String province;private String city;// getter()/setter()
}

后台接收:

@PostMapping(value = "/login5")
public UserVo login5(@RequestBody UserVo userVo, @RequestBody Address address) {
    return userVo;
}

请求错误。

解决方案:把 UserVo 和 Address 封装为一个大对象 Combine

public class Combine {
    private UserVo userVo;private Address address;
}

后台接收:

@PostMapping(value = "/login")
public Combine login(@RequestBody Combine combine) {
    return combine;
}

3.3 @RequestBody 接收 JSON 字符串数组

public class User {
    private String id;private String username;// getter()/setter()
}
public class Team {
    private String teamName;// 或者是数组: private User [] users;private List<User> users;// getter()/setter()
}

前端请求:

在这里插入图片描述

后端接收:

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping("/team")public Team team(@RequestBody Team team) {
    return team;}
}

springboot 接收List 入参的几种方法

  相关解决方案