先来熟悉下,前端给后端传值的时候有哪几种方式呢?
- HTML标签的form表单提交(GET、POST方式提交)
- Jquery中的ajax方式提交
- 其他的一些如axios(其实就是封装了ajax)
那么说到了Form表单提交,有必要再去了解下,form表单中,提交方式get和post的区别,这里借用一下网上的图来说明一下。
一个HTTP请求,一般包含以下几个内容:
-
请求行(请求地址…)
-
请求头(头部信息…)
-
请求体(可存储数据)
-
请求空行(HTTP协议数据规范)
那么GET和POST提交方式的区别如上图所示:
- 在请求行中,POST方式不会把数据暴露出来,GET会暴露数据,但是GET方式处理数据的效率优于POST方式,根据情况取舍
- POST方式会把数据处理好后,放入到请求体中
Content-Type,内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定文件接收方将以什么形式、什么编码读取这个文件 --百度百科
说白了,就是比如两个人要进行数据的传递,那么这两个人定义好一个内容类型,一方以这种内容类型传递数据,另一方要遵守规定也以这种内容类型进行读取传输过来的数据。
Content-Type有哪些类型:(常见的)
-
application/x-www-form-urlencoded: 标准表单编码类型
请求体中的数据会以普通表单形式(键值对)发送到后端
-
multipart/form-data:类型
它会将请求体的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件
-
application/json: json类型
请求体中的数据会以json字符串的形式发送到后端
-
application/xml: xml类型
-
Content-Type: application/json时
let params = {'title':'test', 'sub' : [1,2,3]} axios({ method: 'post', url: '/api/lockServer/search', params }) 最终发送的请求是 {"title":"test","sub":[1,2,3]} -
Content-Type: application/x-www-form-urlencoded 时
不设置Content-Type,使用URLSearchParams
const params = new URLSearchParams(); params.append('param1', 'value1'); params.append('param2', 'value2'); axios.post('/foo', params);使用qs库编码数据(需要设置Content-Type)
import qs from 'qs'; const data = { 'bar': 123 }; const options = { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, data: qs.stringify(data), url, }; axios(options); -
Content-Type: multipart/form-data时
采用FormData方式
import axios from 'axios' let data = new FormData(); data.append('code','1234'); data.append('name','yyyy'); axios.post(`${this.$url}/test/testRequest`,data) .then(res=>{ console.log('res=>',res); })
一般我们在开发的过程中需要注意客户端发送请求(Request)时的Content-Type设置,特别是使用ajax的时候,如果设置得不准确,很有可能导致请求失败。比如在spring中,如果接口使用了@RequestBody,spring强大的自动解析功能,会将请求实体的内容自动转换为Bean,但前提是请求的Content-Type必须设置为application/json,否正就会返回415错误。
注:415 错误是 Unsupported media type,即不支持的媒体类型。
建议:
- 如果是一个restful接口(json格式),一般将Content-Type设置为application/json; charset=UTF-8;
- 如果是文件上传,一般Content-Type设置为multipart/form-data
- 如果普通表单提交,一般Content-Type设置为application/x-www-form-urlencoded
服务端响应(Response)的Content-Type最好也保持准确,虽然一般web开发中,前端解析响应的数据不会根据Content-Type,并且服务端一般能自动设置准确的Content-Type,但是如果乱设置某些情况下可能会有问题,比如导出文件,打开图片等。如果在spring项目里使用@ResponseBody,spring会将响应的Content-Type设置为application/json;charset=UTF-8;,可能会导致文件无法导出,需要注意下。
建议:
- 一般情况下不需要显示设置;
- 如果是文件导出,Content-Type 设置为 multipart/form-data,并且添加一个Content-Disposition设置为attachment;fileName=文件.后缀。(用springboot做过文件下载的应该比较熟悉这点)
- 注:Content-Disposition是Content-Type的扩展,告诉浏览器弹窗下载框,而不是直接在浏览器里展示文件。因为一般浏览器对于它能够处理的文件类型,如txt,pdf 等,它都是直接打开展示,而不是弹窗下载框。
进入正题。。
首先,学过springmvc应该知道,后端常用接收参数的几种方式有:
-
无注解 (普通类型、对象类型)
-
@RequestParam
-
@RequestBody
-
@PathVariable
-
HttpServletRequest
其中有一个小细节,可能大多数人都忽略了,那就是:
当使用注解方式+限制必传(默认的就是必传)时,前端必须给我传这个参数,否则直接告诉他4xx(客户端错误),这个大家应该都知道。当使用无注解和HttpServletRequest时,如果前端没有把值传过来或者数据丢了,那么后端不会去报错,而是给一个默认值(普通类型(比如int类型默认0)和引用类型的默认值(null)),继续执行代码。
无注解接收参数先准备一个User类,方便后面使用
@Data
public class User {
private String name;
private String age;
}
// 普通类型
@GetMapping("/test1")
public void test1(String name, Integer age) {
System.out.println(name);
System.out.println(age);
}
// 对象类型
@GetMapping("/test2")
public void test2(User user) {
System.out.println(user);
}
普通类型
数据放入请求行
还不清楚请求行的,去上面看一下介绍就明白了。
控制台打印:
zs 21数据放入请求体
控制台打印:
zs 21对象类型 数据放入请求行
控制台打印:
User(name=zs, age=21)数据放入请求体
控制台打印:
User(name=zs, age=21)结论
无论是普通类型还是对象类型,放入请求行还是请求体,都能把数据读取出来。
@RequestParam注解使用@RequestParam注解时要注意,此注解一般使用在普通类型的属性上,因为对象类型的时候,你的值根本就不好传,当然像一些好传值的类型是可以用的,比如 List、数组…
@GetMapping("/test3")
public void test3(@RequestParam String name, @RequestParam String age) {
System.out.println(name);
System.out.println(age);
}
@GetMapping("/test4")
public void test3(@RequestParam List ids) {
System.out.println(ids);
}
普通类型
放入请求行
控制台打印:
zs 21放入请求体
控制台打印:
zs 21对象类型 放入请求行
控制台打印:
[1, 2, 3]放入请求体
控制台打印:
[1, 2, 3]结论
使用@RequestParam注解时,和不使用注解一样,都可以接收到请求行、请求体中的数据。
但是要注意下,不使用注解时,不传值时会给属性默认值。
@RequestBody使用@RequestBody,后端接收Content-Type是application/json类型的json数据,且数据都是放入请求体的,Content-Type如果不对,则会报错,一般就是不支持Content-Type类型之类的,协商一下改一下就行。
@GetMapping("/test6")
public void test6(@RequestBody User user) {
System.out.println(user.toString());
}
@GetMapping("/test7")
public void test7(@RequestBody Map map) {
System.out.println(map.toString());
}
@GetMapping("/test8")
public void test8(@RequestBody List ids) {
System.out.println(ids);
}
@GetMapping("/test9")
public void test9(@RequestBody int[] ids) {
System.out.println(ids);
}
test6(User类型)
请求体中的数据
控制台输出:
User(name=zs, age=21)test7(Map类型)
控制台输出:
{name=zs, age=21}
test8(集合类型)
控制台打印:
[1, 2, 3]
这里一定是要注意的:
当我们接收的是集合或者数组的时候,前端传过来的一定不能是json对象,而是一个json数组,切记!!!
test9就不演示了,和test8是一模一样的,也是接收的json数组。
@PathVariable这种就是直接把参数值给拼接到url中,然后后端直接读取就可以了
当不传值时,由于url匹配不到,就直接404了
@GetMapping("/test5/{id}")
public void test5(@PathVariable Integer id) {
System.out.println(id);
}
控制台打印:
1HttpServletRequest
接收的参数就是一个HttpServletRequest对象,然后从这个Request对象里取数据即可。
@GetMapping("/test10")
public void test10(HttpServletRequest request) {
String name = request.getParameter("name");
String age = request.getParameter("age");
String age2 = request.getParameter("age2");
System.out.println(name);
System.out.println(age);
System.out.println(age2);
}
请求行
控制台打印:
zs 21 null请求体
控制台输出:
zs 21 null结论:
和其他方式一样,都可以从请求行和请求体中读取到数据。数据没有传输过来那就采用默认值的方式。
如果请求行和请求体都传数据平时应该遇不到这样的问题,但是还是了解下吧,不然后面真遇到了,就直接蒙圈了。
-
请求行和请求体中传输的数据都是不同名的:
这种是没有任何问题的,相当于是我一些数据从请求行传过去,一些数据从请求体中传过去,能正常接收
-
请求行和请求体中传输的数据有同名的:
这种请求,我试了两个类型,其他的暂时没有试。如果是String类型的参数,那么它会先读取到请求体中的数据,请求行如果有同名的属性,那么会把值 拼接到从请求体读取到的数据上,逗号分割。如zs,ls
如果是Integer类型,那么它只会以请求体中的数据为准(同样是先读取请求体,然后如果请求行中有同名,直接过滤掉了–个人理解)
其他类型没有深究,有兴趣的可以去试试。如果后面遇到这种问题,这是一种排错的方式,仅供参考。



