目录
结论:@RequestParam---url后面的参数 @RequestBody post 是body参数
先看User类:
前言
get请求与post请求接收参数的形式是不一样的,要想搞清楚 @RequestParam与@RequestBody的用法,就必须知道get请求和post请求的差异。
get请求与post请求
共同点:本质上都是TCP连接
区别:
1、参数位置
由于GET请求是直接把请求参数拼接到url上,浏览器往往会对url长度进行限制,所以会对请求参数的大小有所限制,而POST请求是把请求参数放到body中,因此大小没有限制。
表单的 method 和 enctype属性
具体来说, 表单提交其实有两种方式, get
和 post
, 可以通过 method
属性指定. 比如下面是一个 post 方式提交的表单:
<form action="xxx.jsp" method="post">...</form>
而这样则是 get 方式:
<form action="xxx.jsp" method="get">...</form>
你可能注意到前面的表单是没有指定 method 属性, 那么则使用缺省方式, 在 html 规范中, 缺省即为 get 方式.
其实表单中还有另外一个重要属性, 也就是 enctype
, 它是 encoding type 的缩写, 意思即为"编码类型"(或"内容类型(content type)")
具体也有两个值:
application/x-www-form-urlencoded
multipart/form-data
缺省即为 application/x-www-form-urlencoded
. 所以, 前面例子中的表单等价为:
<form action="form_get_target_default.jsp" method="get" enctype="application/x-www-form-urlencoded">...</form>
综上, 表单 get 方式的提交就是使用 urlencoded 把表单数据以 url 查询字符串的形式放到 url 末尾发送到服务端。
表单提交时,请求头设置为application/x-www-form-urlencoded,POST和GET的区别
请求方式 | 编码方式 | 参数存放位置 | 相同点 |
GET | application/x-www-form-urlencoded | URL中: form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割 | get 方式中queryString的值,和post方式中 body data的值都会被Servlet接受到并转化到Request.getParameter()参数集中,所以@RequestParam可以获取的到。 |
POST | application/x-www-form-urlencoded | Body中:浏览器把form数据封装到http body中,然后发送到server |
2、GET请求
GET 请求不存在请求实体部分,键值对参数放置在 URL 尾部,浏览器把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串追加到url后面,用?分割,加载这个新的url。因此请求头不需要设置 Content-Type 字段,设置了也不会去使用。值得一提的是,GET 参数的编码方式是无法人为干涉的,这导致了不同浏览器有不同的编码方式。
3、POST请求
Http Header中有键值对,有一个键值对就是Content-Type,其值一般有这三种:
application/x-www-form-urlencoded | 数据被编码为名称/值对。这是标准的编码格式。默认行为。会将表单内的数据转换拼接成 key-value 对(非 ASCII 码进行编码) |
multipart/form-data() | 数据被编码为一条消息,页上的每个控件对应消息中的一个部分,必须让 表单的 enctype 等于 multipart/form-data。 |
text/plain(接口测试文档里标的是raw) | 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符(中文不进行编码)。主要有application/json、text/xml等 |
ApiPost的用法
ApiPost的body的类型主要由三种类型的参数: form-data、x-www-form-urlencoded、raw。
由于post请求的参数才放到请求体(Body)里面,get的请求参数一般都直接跟在url后面,所以这里Body里面参数都是指的post请求参数,那post请求测试时怎么判断选择哪个格式的来发送参数呢?
1、form-data(multipart/form-data),支持上传文件的表单类型:
form-data对应着http请求中的Content-Type=multipart/form-data, 一般在表单中如果需要进行文件上传时,就需要使用该格式。
它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件File。当上传的字段是文件时,会有Content-Type来说明文件类型;content-disposition用来说明一些字段信息;
由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件
2、 x-www-form-urlencoded,表单类型的接口请求:
对应着http请求中的Content-Type为application/x-www-from-urlencoded,会将表单内的数据转换为键值对,比如,name=python&age = 22,这种方式只能以键值对形式发送参数,一般如果不指定content-type,默认便是application/x-www-form-urlencoded,
如b站的注册接口采用的就是这种方式发送消息,如下图,通过抓包获取到Content-Type为application/x-www-from-urlencoded,参数数据就是以键值对的形式发送的。
3、raw(支持各种原生的类型,JSON类型的接口请求),如:Content-Type=application/json时,则可以使用这种方式,这个是实际接口测试中,使用到最多的方式了。越来越多的人把它作为请求头,用来告诉服务端消息主体是序列化后的 JSON 字符串
他是可以上传任意格式的参数,可以上传text、json、xml、html、js
get请求
get+application/json
1.无注解情况
接口如下:
各种请求状况如下:
- application/json
①不传参
结果:
ApiPost:提示服务器错误,也就是后端代码出了错误
IntelliJ IDEA:出现错误
报错:Optional int parameter 'a' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.
翻译:
分析:后端设置的参数的类型如果是基本数据类型 如 int, long,char 等8中基本类型,并且前端没有传这个参数,那么就会报这一个错误。这个意思是说,参数xxx是可选的,但是它本身是个基本类型数据,没有办法被转换成null值,让你考虑它把变为当前基本类型的包装类如Long,Integer等,那么你把它转换成对应的包装类就可以了。
java基本数据类型:
- 整数类型:byte,1字节,8位,最大存储数据量是255,存放的数据范围是-128~127之间。
- 整数类型:short,2字节,16位,最大数据存储量是65536,数据范围是-32768~32767之间。
- 整数类型:int,4字节,32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。
- 整数类型:long,8字节,64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。
- 浮点类型:float,4字节,32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。
- 浮点类型:double,8字节,64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。
- 字符型:char,2字节,16位,存储Unicode码,用单引号赋值。
- 布尔型:boolean,只有true和false两个取值
②传参
考虑到上面的的情况是无参数的情况,因此,加上参数是不是就可以使用了呢?下面测试一下。
结果:
apiPost:
Intellig IDEA :
同样的错误。
③传参,但接口是Integer,接口不再是基本数据类型int
结果:
IntelliJ IDEA:
ApiPost:
总结:
测试时,无论请求中加不加参数,后端的接口都看不到a变量,也就是前端的键a。说明不加注解的get方法不接收application/json形式的请求。
接口参数为基本数据类型时,由于看不到json中的参数,则报错。接口封装类型时,接收不到就默认为null。
④传参,接口为自定义类型User
接口如下:
测试如下:
结果:
ApiPost:
IntelliJ IDEA:
总结:说明了不接@RequestParam或者@RequestBody注解,接口看不到前端的json数据(body->application/json)。
那么问题来了,get请求+application/json的请求方式时不推荐的,很少有人这样用,如果非要用,那怎么样才能实现呢?其实就是加注解,加什么注解呢?
接下来我们用测试以下加注解的情况。
2.有注解情况
我们以后写接口,尽量避免用基本数据类型做形参。所以我们只测试封装数据类型和自定义数据类型。
①get+@RequestParam注解+Integer形参
接口如下:
结果:
ApiPost:
现在提示的意思是,错误的请求,也就是我们的请求方式是有问题的,也就是说get请求中标注为@RequestParam的参数,不能application/json的方式进行请求。
IntelliJ IDEA
报错:Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required Integer parameter 'a' is not present]。
翻译:必需的整型参数a没有被提供。
①get+@RuquestBody注解+Integer形参
结果:
ApiPost:错误的请求
IntrlliJ IDEA:
报错:
2023-12-15 11:42:50.796 WARN 132248 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.Integer` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.Integer` out of START_OBJECT token at [Source: (PushbackInputStream); line: 1, column: 1]]
意思是:无法反序列化“java.lang.Integer”实例。这里大家可能不是很明白为什么用@RequestParam和@RequestBody提示的错误信息是不一样的。其实从服务端提供的错误信息我们也可以看出为什么。
使用@RequestParam时,提示信息为参数a没有被提供,也就是尽管我们前面传递了参数a,但是后端时看不到的。使用@RequestBody显然是看到了a,所以才会说无法反序列化Intger类型的实例。反序列化就是将json字符串反序列化为Integer实例。
总结:
经过上面的测试,我们发现,对于get+application/json这种形式的请求,后端不加@RequestParam和@RequestBody注解是接收不到参数的,认为没有传参。
于是int等基本类型的形参就会报错,Integer封装类型的参数以及自定义Use类型自动转换为null。
加@RequestParam同样看不见参数,认为没传参。加@RequestBody看得到参数,能顺利接收自定义类型User,因为它可以将User的json字符串的反序列化为User对象(测试如下)。
结果:
ApiPost:
IntelliJ IDEA:
成功!!!
所以说,@RequestBody的作用就是,将application/json字符串反序列化为自定义类型。@RequestParam没有这个作用,所以他不能接受application/json类型的请求。
get+