目录
书接上文:Java EE 进阶:4.Spring Web MVC 入门(第一弹)~~
2.4.7 传递 JSON 数据
JSON 概念
JSON:JavaScript Object Notation 【JavaScript 对象表示法】
简单说:JSON 是 客户端和服务端进行交互的一种数据格式
JSON是一种轻量级的数据交互格式. 它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。--百度百科
简单来说:JSON就是一种数据格式, 有自己的格式和语法, 使用文本表示一个对象或数组的信息, 因此 JSON本质是字符串. 主要负责在不同的语言中数据传递和交换.
类似于:
• 国际通用语言-英语
• 中国56个民族不同地区的通用语言-普通话
有自己的语法, 其他语言也认识.用 JSON 表示对象类似于👇
{ "name":"CoderYanger", "age":18, "gender":"男" }对比 Java 表示对象👇
UserInfo user=new UserInfo(); user.setName();JSON 与 JavaScript 的关系
没有关系, 只是语法相似, js开发者能更快的上手而已, 但是他的语法本身比较简单, 所以也很好学
JSON 语法
JSON 是一个字符串,其格式非常类似于 JavaScript 对象字面量的格式
我们先来看一段JSON数据{ "squadName": "Super hero squad", "homeTown": "Metro City", "formed": 2016, "secretBase": "Super tower", "active": true, "members": [{ "name": "Molecule Man", "age": 29, "secretIdentity": "Dan Jukes", "powers": ["Radiation resistance", "Turning tiny", "Radiation blast"] }, { "name": "Madame Uppercut", "age": 39, "secretIdentity": "Jane Wilson", "powers": ["Million tonne punch", "Damage resistance", "Superhuman reflexes"] }, { "name": "Eternal Flame", "age": 1000000, "secretIdentity": "Unknown", "powers": ["Immortality", "Heat Immunity", "Inferno", "Teleportation", "Interdimensional travel"] }] }也可以压缩表示:
{"squadName":"Super hero squad","homeTown":"Metro City","formed":2016,"secretBase":"Super tower","active":true,"members":[{"name":"Molecule Man","age":29,"secretIdentity":"Dan Jukes","powers":["Radiation resistance","Turning tiny","Radiation blast"},{"name":"Madame Uppercut","age":39,"secretIdentity":"Jane Wilson","powers":["Million tonne punch","Damage resistance","Superhuman reflexes"]},{"name":"Eternal Flame","age":1000000,"secretIdentity":"Unknown","powers":["Immortality","Heat Immunity","Inferno","Teleportation","Interdimensional travel"]}]}和上面描述的数据一样, 只不过上面的进行了格式化, 更易读.
JSON 的语法:
1. 数据在键值对(Key/Value) 中
2. 数据由逗号 , 分隔
3. 对象用 {} 表示
4. 数组用 [] 表示
5. 值可以为对象, 也可以为数组, 数组中可以包含多个对象6.使用一个字符串来表示对象或者数组等
7.属性和值之间,使用:分割
8.键值对之间,使用,分割
JSON 的两种结构
1. 对象: 大括号 {} 保存的对象是一个无序的键值对集合. 一个对象以左括号 { 开始, 右括号 }
结束。每个"键"后跟一个冒号 : ,键值对使用逗号, 分隔
2. 数组: 中括号 [] 保存的数组是值(value)的有序集合. 一个数组以左中括号[ 开始, 右中括
号] 结束,值之间使用逗号 , 分隔。所以, 以下都是合法的JSON数据
{"name":"admin","age":18} ["hello", 3.1415, "json"] [{"name":"admin","age":18},{"name":"root","age":16},{"name":"张三","age":20}]可以使用在线JSON格式化工具来进行校验和书写:在线JSON校验格式化工具(Be JSON)
这个在线工具只是一个例子,大家在网上搜索:在线 JOSN,有好多工具可以用的~~
时间戳
之前有讲过:Java EE:2.多线程-初阶(第九弹):多线程案例-定时器
时间戳(Unix timestamp)转换工具 - 在线工具
时间戳:从 1970年1月1日经过的秒数
JSON 字符串和 Java 对象互转
JSON本质上是一个字符串, 通过文本来存储和描述数据
Spring MVC框架也集成了JSON的转换工具, 我们可以直接使用, 来完成JSON字符串和 Java 对象的互转
本质上是 jackson-databind 提供的功能, Spring MVC框架中已经把该工具包引入了进来, 咱们直接使用即可, 如果脱离Spring MVC使用, 需要引入相关依赖<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.5</version> </dependency>JSON的转换工具包有很多, jackson-databind 只是其中的一种.
public class JSONUtils { private static ObjectMapper objectMapper = new ObjectMapper(); public static void main(String[] args) throws JsonProcessingException { Person person = new Person(); person.setId(5); person.setName("zhangsan"); person.setPassword("123456"); //对象转为JSON字符串 String jsonStr = objectMapper.writeValueAsString(person); System.out.println("JSON字符串为:"+jsonStr); //JSON字符串转为对象 Person p = objectMapper.readValue(jsonStr,Person.class); System.out.println("转换的对象id:"+p.getId()+",name:"+p.getName()+",password:"+p.getPassword()); } }使用ObjectMapper 对象提供的两个方法, 可以完成对象和JSON字符串的互转
writeValueAsString: 把对象转为JSON字符串
readValue: 把字符串转为对象常见的 JSON 和对象的转换工具:
1.gson
2.fastjson
3.jackson(我们拿这个举例,因为 Spring 已经集成进来了,我们啥也不用干了👇)
正常我们打印数据👇
使用 JSON 👇
package com.bite.spring.demo; import org.junit.jupiter.api.Test; public class JsonTest { @Test//来自junit,测试人员经常使用的注解 void testJson(){ System.out.println(12333); } }可以看到,运行结果是一样的👇
与 main 方法不同,因为一个类里只能有一个 main 方法,但是测试方法可以有多个👇
package com.bite.spring.demo; import org.junit.jupiter.api.Test; public class JsonTest { @Test//来自junit,测试人员经常使用的注解 void testJson(){ System.out.println(12333); } @Test void testJson2(){ System.out.println(55666); } }
下面演示一下 对象 转 JSON👇
package com.bite.spring.demo; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; public class JsonTest { @Test void testObjectToJson() { ObjectMapper objectMapper = new ObjectMapper(); //创建Java对象 UserInfo userInfo = new UserInfo(); userInfo.setName("CoderYanger"); userInfo.setAge(21); userInfo.setGender(1); //对象转json String s = objectMapper.writeValueAsString(userInfo); System.out.println(s); } }转换结果👇
再来演示 JSON 转 对象👇
package com.bite.spring.demo; import org.junit.jupiter.api.Test; import tools.jackson.databind.ObjectMapper; public class JsonTest { @Test void testJsonToObject() { ObjectMapper objectMapper = new ObjectMapper(); //定义一个json字符串 String s="{\"age\":21,\"gender\":1,\"name\":\"CoderYanger\"}"; //json转对象 UserInfo userInfo=objectMapper.readValue(s,UserInfo.class); System.out.println(userInfo); } }转换结果👇
JSON 优点
1. 简单易用: 语法简单,易于理解和编写,可以快速地进行数据交换
2. 跨平台支持: JSON可以被多种编程语言解析和生成, 可以在不同的平台和语言之间进行数据交换和传输
3. 轻量级: 相较于XML格式, JSON数据格式更加轻量级, 传输数据时占用带宽较小, 可以提高数据传输速度(闲聊:PB(Protobuf) 比 JSON 轻量)
4. 易于扩展: JSON的数据结构灵活,支持嵌套对象和数组等复杂的数据结构,便于扩展和使用
5. 安全性: JSON数据格式是一种纯文本格式,不包含可执行代码, 不会执行恶意代码,因此具有较高的安全性
基于以上特点, JSON在Web应用程序中被广泛使用, 如前后端数据交互、API接口数据传输等.传递 JSON 对象
接收JSON对象, 需要使用 @RequestBody 注解
RequestBody: 请求正文,意思是这个注解作用在请求正文的数据绑定,请求参数必须在写在请求正文中
后端实现://传递JSON @RequestMapping("/r9") public String r9(@RequestBody UserInfo userInfo){ System.out.println(userInfo.getName()); return userInfo.toString(); }使用Postman来发送 json 请求参数:
可以看到, 后端正确接收了
通过Fiddler观察一下请求参数:当然,名字还是要一一对应的,如果我们把传的参数 name 改成 name11,就会传不过去👇
可以发现此时 name 就对应不上了,就变成 null 了
尝试去除掉 @RequestBody 试试
//传递JSON @RequestMapping("/r9") public String r9( UserInfo userInfo){ System.out.println(userInfo.getName()); return userInfo.toString(); }请求响应结果如下:
后端未能成功给Person对象赋值.
2.4.8 获取URL中参数@PathVariable
我们随便在今日头条中挑两个文章👇
https://www.toutiao.com/article/7697851229967303219/ https://www.toutiao.com/article/7697896887999611435/闲聊:路径参数 VS 请求参数
路径参数:URL 上除去查询字符串,也就是?后面那一串,上述的 7697851229967303219 和 7697896887999611435 就属于路径参数了
请求参数:URL 的查询字符串和请求正文,类似 ?name=zhangsan&age=15
参数可以放在哪里??
①URL 资源路径
②URL 查询字符串
③ header
④请求正文body
可以说除了 GET 和 POST 其余地方都可以放参数👇
答疑:
Q1:page 分页查询是属于路径参数是吧?
不一定
像 http://XXX/1/ 这里的 1 就是路径参数
像 http://XXX?page=1 这里的 1 就是请求参数
其实路径参数用的很少,一般都是唯一的 ID 才用的上,比如头条上的文章、京东上的商品
我们主要掌握如何获取参数就可以了,应用为主,这些我们了解即可~~
Q2:请求参数都是像键值对那种吗?
除了键值对,还有可能是 json
这两个网址最后面的那串数字我们可以简单理解为文章ID,我们通过这个 URL 去请求的时候发送到了后端,那后端如何拿到这个数据呢?肯定是根据文章ID,然后拿着文章ID 去数据库里查,查完之后返回给我们的前端,那么现在问题来了,我们前面学的内容都做不到从这个 URL 中取值,接下来我们学习如何从 URL 中取值~~
path variable: 路径变量
和字面表达的意思一样, 这个注解主要作用在请求URL路径上的数据绑定
默认传递参数写在URL上,SpringMVC就可以获取到
后端实现代码://从URL中获取参数 @RequestMapping("/article/{articleId}") public String r10(@PathVariable Integer articleId){ return "获取文章ID:"+articleId; }这个 @PathVariable 表示路径的变量,加上之后表示:只要 你这个数字 是符合这个格式的,我们就把你赋值给 acticleId
使用Postman发送请求:
但如果我们输入 aaa 👇
因为我们是使用 Integer 接收的,所以就会不匹配,如果是使用 String 接收就 OK 了
另外,从 URL 中获取参数也可以是多个参数👇
@RequestMapping("/article/{type}/{articleId}") public String r11(@PathVariable Integer articleId,@PathVariable String type){ return "获取文章ID:"+articleId+",type:"+type; }使用Postman发送请求:
注意:@PathVariable 在每个参数前面都要写,这一点在源码部分也有体现,目标是参数~~
那如果不写 type 呢??将上方代码注释掉后再来尝试一下👇
我们发现这是不可以的👇
404 而不是 400 表示没有任何一个能与他匹配,这就说明 @PathVariable 也是必传参数,并且,这个改不了!跟前面学的 @RequestParam 不同,如果这个能改,那就乱套了
假如我们都写成 required = false👇,那我们只传一个值的时候,这个值到底是 type 还是 articleId?别说 Spring 分不出来了,咱自己都分不出来~~
@RequestMapping("/article/{type}/{articleId}") public String r11(@PathVariable(required = false) Integer articleId,@PathVariable(required = false) Integer type){ return "获取文章ID:"+articleId+",type:"+type; }用 Postman 发送请求试试看👇
发现确实匹配不上,分不出来这个值是谁的~~
当然,我们也可以对其进行重命名,比如拿到 type 赋值给 articleType 👇
@RequestMapping("/article/{type}/{articleId}") public String r11(@PathVariable Integer articleId,@PathVariable("type") Integer articleType){ return "获取文章ID:"+articleId+",type:"+articleType; }用 Postman 发送请求演示👇
答疑:这里的 Integer 会不会超位数??
要是数字太大了,超过 Integer 的范围的话,当然也会溢出的~~
其实我们的 Spring 接收到的都是 String 类型,不管你使用 int、Integer、Boolean 类型……
都是 String ,然后把这个 String 类型转成相应的类型,那么能不能转呢?能转就 OK,不能转就报错
2.4.9 上传文件@RequestPart
后端代码实现:
//上传文件 @RequestMapping("/r12") public String r12(MultipartFile file){ System.out.println(file.getOriginalFilename()); return "文件获取成功"; }Multipart 表示多个部分,这个就记住就行了,打印 getOriginalFilename 和打印 name 有什么区别呢?我们看一下结果就知道了~~
使用Postman发送请求:
上传代码
通过视频中的操作我们发现后端也打印出来了刚刚上传文件的名称
那么这个文件我们怎么操作呢??
文件上传的时候,我们拿到一个文件的 InputStream 来拿它的值,然后再用这个 InputStream,如果想写,就用 OutputStream 来写,这就跟我们 Java EE 初阶文件部分的内容一样了👇
Java EE:4.文件操作和IO(第二弹):数据流(上篇)
Java EE:4.文件操作和IO(第三弹):数据流(下篇)
当然,我们的文件也给我们提供了一些简单的方法,比如说我们可以通过 transferTo() 直接上传文件,后面可以跟地址,也可以跟文件,我们用文件尝试一下👇
最终发现成功上传到了 D盘的 temp 路径下~~
所以后边咱们如果说有做到文件相关的东西,我们就可以用户上传,上传完之后,我们就可以把它存到我们自己的服务器上的某一个地址里了~~
那么这个 transferTo 做了一件什么事情呢??
其实做的事情就是我们在前面学习内容的一个封装👇
有一个 file copy,复制当前文件下的 InputStream 然后输出到 目标dest 的 OutputStream
其实我们的 Spring 对于文件操作也给我们做了一些封装,我们直接调用这个方法就可以了,文件上传也不用我们再写了
@RequestPart 重命名
当我们传的名称与后端代码不同时(将 file 改为了 file11)👇
发现报了空指针的错误,此时我们就可以通过 @RequestPart 将 file11 重命名为 file👇
//上传文件 @RequestMapping("/r12") public String r12(@RequestPart("file11") MultipartFile file) throws IOException { System.out.println(file.getOriginalFilename()); //文件上传 file.transferTo(new File("D:\\temp\\"+file.getOriginalFilename())); return "文件上传成功"; }此时我们发现上传成功了👇
我们也可以同时观察一下 Fiddler 什么情况👇
下面的乱码都是 文件的二进制信息~~
2.4.10 获取 Cookie/Session
回顾 Cookie
Java EE:7.网络原理- HTTP/HTTPS(第三弹):HTTP 请求-请求报头+请求正文部分有讲过~~
打个比方,入学时,学生填写个人信息,填了(姓名、身份证号、联系人、家庭住址、高中学校、初中学校……),这些信息就是 Session
学校就会建档,给你发个学生证,你以后出入学校携带的身份证就是 Cookie,里面记录了你的姓名、班级、大学名称、学号……
再或者,一个医生每天要看上百个号,记不住病人,病人要复诊,医生需要知道病人的信息
1.去医院先建档(姓名、身份证号、家庭住址……)
2.医院给你发了一张卡片(卡片上有你的号码) ,这就是用户的诊疗卡,也是 Cookie
3.你拿着诊疗卡 挂号、看病、取药……
医生是如何看到你的信息的?
根据诊疗卡上的 ID(病人 ID),去查询资料→去医院的服务器上查询→存储了病人的信息,这就是 Session
通过以上两个例子总结出:
Cookie 是存储在客户端上的
Session 是存储在服务器上的
为啥要有 Cookie 和 Session ?
因为 HTTP 协议自身是属于 "无状态" 协议.跟上面的例子中的医生一样,看的病人那么多,没啥记忆力,根本记不住~~这里的 HTTP 也一样,我们会遇到:
HTTP 是无状态的,没啥记忆力,不知道你曾经请求过它~~
该服务是无状态的,也没啥记忆力,但这却是个好事儿,不然就糟糕了:
如果用户在 服务器 1~3 得到的结果是一致的,就是无状态的,我们就可以方便扩容(加服务器)
如果用户1只能访问服务器1,用户2只能访问服务2……如果服务器 1~3 已经饱和了,那么你再扩容机器也没用了
"无状态" 的含义指的是:
默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系.
但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的.
例如登陆网站成功后, 第二次访问的时候服务器就能知道该请求是否是已经登陆过了.我第一次访问京东,你不认识我,我不说啥,我都登录了你还不认识我,那我就要开始🐎了~~
上述图中的 "令牌" 通常就存储在 Cookie 字段中.
比如去医院挂号
1. 看病之前先挂号. 挂号时候需要提供身份证号, 同时得到了一张 "就诊卡", 这个就诊卡就相当于患
者的 "令牌".
2. 后续去各个科室进行检查, 诊断, 开药等操作, 都不必再出示身份证了, 只要凭就诊卡即可识别出当前患者的身份.
3. 看完病了之后, 不想要就诊卡了, 就可以注销这个卡. 此时患者的身份和就诊卡的关联就销毁了. (类似于网站的注销操作)
4. 又来看病, 可以办一张新的就诊卡, 此时就得到了一个新的 "令牌"
此时在服务器这边就需要记录"令牌"信息, 以及令牌对应的用户信息, 这个就是 Session 机制所做的工作.理解 Session
我们先来了解一下什么是会话.
会话: 对话的意思在计算机领域, 会话是一个客户与服务器之间的不中断的请求响应. 对客户的每个请求,服务器能够识别出请求来自于同一个客户. 当一个未知的客户向Web应用程序发送第一个请求时就开始了一个会话.
当客户明确结束会话或服务器在一个时限内没有接受到客户的任何请求时,会话就结束了.
比如我们打客服电话
每次打客服电话, 是一个会话. 挂断电话, 会话就结束了
下次再打客服电话, 又是一个新的会话.
如果我们长时间不说话, 没有新的请求, 会话也会结束.
服务器同一时刻收到的请求是很多的. 服务器需要清楚的区分每个请求是属于哪个用户, 也就是属于哪个会话, 就需要在服务器这边记录每个会话以及与用户的信息的对应关系.
Session是服务器为了保存用户信息而创建的一个特殊的对象.Session的本质就是一个 "哈希表", 存储了一些键值对结构. Key 就是SessionID, Value 就是用户信息(用户信息可以根据需求灵活设计).
SessionId 是由服务器生成的一个 "唯一性字符串", 从 Session 机制的角度来看, 这个唯一性字符串称为 "SessionId". 但是站在整个登录流程中看待, 也可以把这个唯一性字符串称为 "token".
上述例子中的令牌ID, 就可以看做是SessionId, 只不过令牌除了ID之外, 还会带一些其他信息, 比如时间, 签名等.1. 当用户登陆的时候, 服务器在 Session 中新增一个新记录, 并把 sessionId返回给客户端. (通过 HTTP 响应中的 Set-Cookie 字段返回).
2. 客户端后续再给服务器发送请求的时候, 需要在请求中带上 sessionId. (通过 HTTP 请求中的Cookie 字段带上).
3. 服务器收到请求之后, 根据请求中的 sessionId在 Session 信息中获取到对应的用户信息, 再进行后续操作.找到了说明用户之前登录过,直接进去就行了,找不到则重新创建Session, 并把SessionID返回,让你创建用户啥的~~Session 默认是保存在内存中的. 如果重启服务器则 Session 数据就会丢失.
Cookie 和 Session 的区别
• Cookie 是客户端保存用户信息的一种机制. Session 是服务器端保存用户信息的一种机制.
• Cookie 和 Session之间主要是通过 SessionId 关联起来的, SessionId 是 Cookie 和 Session 之间的桥梁
• Cookie 和 Session 经常会在一起配合使用. 但是不是必须配合.
◦ 完全可以用 Cookie 来保存一些数据在客户端. 这些数据不一定是用户身份信息, 也不一定是
SessionId
◦ Session 中的sessionId 也不需要非得通过 Cookie/Set-Cookie 传递, 比如通过URL传递.获取 Cookie
传统获取 Cookie
Spring MVC是基于 Servlet API 构建的原始 Web 框架, 也是在Servlet的基础上实现的
HttpServletRequest , HttpServletResponse 是Servlet提供的两个类, 是Spring MVC方法的内置对象. 需要时直接在方法中添加声明即可.
HttpServletRequest 对象代表客户端的请求, 当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息.
HttpServletResponse 对象代表服务器的响应. HTTP响应的信息都在这个对象中, 比如向客户端发送的数据, 响应头, 状态码等. 通过这个对象提供的方法, 可以获得服务器响应的所有内容
Spring MVC在这两个对象的基础上进行了封装, 给我们提供更加简单的使用方法.httpServletRequest 和 httpServletResponse 这两个东西非常重要,后面我们经常要用到
httpServletRequest 里面包含了请求的所有信息👇
httpServletResponse 里面包含了响应的所有信息👇
用户希望从 http 请求中获得什么,都可以从 httpServletRequest 中获取
用户希望给客户端返回什么,都可以通过 httpServletResponse 来返回
我们简单看一下,我们前面所学的那些东西都可以拿到👇
比如我们写👇
@RequestMapping("/r13") public String r13(HttpServletRequest request, HttpServletResponse response) { String name = request.getParameter("name"); }点进去我们发现返回值是写死的👇
如果我们不想要 String 类型,想转成 Integer,就需要自己转👇
@RequestMapping("/r13") public String r13(HttpServletRequest request, HttpServletResponse response) { String name = request.getParameter("name"); Integer age= Integer.valueOf(name); }我们之前写的代码都没有转,直接就拿来用了,但其实底层操作都是通过 HttpServletRequest 拿的,离不开这个,我们的 Spring 也是它去拿的,只不过我们的 Spring 给我们进行了一些封装,咱们看不到这个过程了,所以我们定义成什么类型,Spring 就会根据我们定义的类型去进行转换,如果是 String 类型就不用转了~~
再来看 response 有啥👇
我们可以设置它的状态码、ContentType、编码等等这些信息……
我们也可以拿到它的 OutputStream 这个流,也可以拿到 Writer,往里写东西,你想返回什么就往里去写
下面讲一下如何拿到 Cookie👇
@RequestMapping("/m10") public String method10(HttpServletRequest request,HttpServletResponse response) { // 获取所有 cookie 信息 Cookie[] cookies = request.getCookies(); //打印Cookie信息 StringBuilder builder = new StringBuilder(); if (cookies!=null){ for (Cookie ck:cookies) { builder.append(ck.getName()+":"+ck.getValue()); } } return "Cookie信息:"+builder; }当然,httpServletRequest 和 httpServletResponse 也不是非要成对出现的,我们需要哪个就写哪个就行了,包括之前的代码,如果我们需要这个就直接写在方法的参数上就可以了,没有先后顺序~~
下面来看 Cookie 通过 Postman 如何设置👇
Postman建立Cookie
其中 Domains 我们通常认为是一个主机的名称或者是域名,所以咱们就写成 http://127.0.0.1:8080/,然后添加就可以了,然后我们可以选择添加 Cookie,我们的 Cookie 是 key-value 形式
我们知道,127.0.0.1= local host,但是在这里是要区分的,127.0.0.1 和 local host 在这里是不相等的
下面我们就要从 Cookie 中获得我们刚才设置的信息了:
两个Cookie👇
name=CoderYanger; Path=/; Expires=Tue, 19 Oct 2027 14:37:20 GMT;age=21; Path=/; Expires=Tue, 19 Oct 2027 14:37:21 GMT;后端代码👇
@RequestMapping("/r13") public String r13(HttpServletRequest request) { Cookie[] cookies=request.getCookies(); if(cookies!=null){ //循环拿到信息 for(Cookie cookie:cookies){ System.out.println(cookie.getName()+":"+cookie.getValue()); } } return "返回Cookie成功"; }启动服务后,用 Postman 发送请求👇
此时我们能看到确实打印出来了👇
从这个例子中, 也可以看出Cookie是可以伪造的, 也就是不安全的, 所以使用Cookie时, 后端需要进行 Cookie校验
简洁获取 Cookie
也有更简洁的方式获取Cookie:使用注解👇
@RequestMapping("/r14") public String r14(@CookieValue("name") String name) { return "从Cookie中获取name的值:"+name; }启动服务后,使用 Postman 发送请求👇
相比之下,这两种方式我个人还是更喜欢上面那种传统获取方式,因为下面这种简洁获取方式只能获取一个,而我们很多时候想获取的都不止一个~~
因为 Cookie 是存储在客户端上的,而我们就是客户端,因此我们就可以去看到,下面我们看一下 B 站的 Cookie,按 F12👇
可以看到会有很多的 Cookie,如果使用注解获取的话,就需要一个个去声明,太麻烦了,所以实际开发中注解获取 Cookie 很少用到~~
答疑:
Q1:一个 Cookie 只能有一个键值对吗??
可以有很多个,我们将刚刚的请求抓包看一下👇
我们会发现它直接给合到一块去了
Q2:Cookie 的键值对可以重复吗??
这个不可以的,比如我们把 age=21 的那个键值对改为 name=LOL👇
保存后我们会发现 name 就合了👇
直接给 CoderYanger 覆盖了~~
我们在做的时候,HTTP 会直接帮我们把它给合到一起
获取 Seesion
Cookie 是存储在客户端的,所以我们可以自己去模拟,但 Session 是存储在服务端的,我们客户端是没办法伪造的,所以我们只能通过服务端去设置
Session 存储和获取
Session是服务器端的机制, 我们需要先存储, 才能再获取
Session 也是基于HttpServletRequest 来存储和获取的Session 存储
我们回顾一下上面的流程👇
服务器先创建一个 Session,创建 Seesion 后然后把 sessionId 通过 Set-cookie返回给客户端了,所以客户端存储的是 sessionId,然后我们服务器再通过 sessionid(在 request 里面),然后再去查找 Session,所以我们这里做的就是:从 Cookie 中获取 sessionId,根据 sessionId 获取 Session 对象👇
//存储session @RequestMapping("/setSession") public String setSession(HttpServletRequest request) { //我们的服务端有能力去根据浏览器的一些信息就知道你是谁了 //然后我们可以通过请求去获取Session //从Cookie中获取sessionId,根据seesionId获取Session对象 //如果sessionId不存在,则创建 HttpSession session=request.getSession(); //默认存储在内存中 //登录的用户名称 session.setAttribute("userName","CoderYanger"); session.setAttribute("age",21); return "设置session成功"; } //获取session @RequestMapping("/getSession") public String getSession(HttpServletRequest request) { //我们的服务端有能力去根据浏览器的一些信息就知道你是谁了 //然后我们可以通过请求去获取Session //从Cookie中获取sessionId,根据seesionId获取Session对象 HttpSession session=request.getSession(false);//存了肯定能找到,没存肯定找不到,找不到就没必要存了,所以false //如果用户登录,session有值,未登录,session为null if(session==null){ return "用户未登录"; }else{ //从session中获取登录用户的信息 String userName=(String)session.getAttribute("userName"); return "登录用户为:"+userName; } }注意,这个 getSession() 有两个方法👇
一个是有参数的,一个是无参数的~~其中无参就默认为 true
true:没有获取到 session,返回一个空的 Session 对象
false:没有获取到 session,返回 null
void setAttribute(String name, Object value): 使用指定的名称绑定一个对象到该 session 会话
启动服务后,我们用浏览器试一下👇
因为 浏览器 和 Postman 是两个不同的客户端,所以我们再用 Postman 发送设置一次👇
再来对比两者差异👇
我们会发现二者的 sessionId 是不同的,类似的,如果我再通过一个浏览器访问,也会生成不同的……服务器内存会存储很多的用户信息
当我们的客户端收到这个 Set-Cookie 的时候,就会做一些处理了,先看看浏览器做了什么处理👇
发现浏览器就会存储这个 sessionId 了,当然,Postman 也会存储👇
下面我们再发起请求的时候它就会带着这个 sessionId 走了
下面我们再看 getsession👇
我们抓包发现,浏览器确实没有有 Postman 的 sessionId,而是它自己的👇
再来看看 Postman 的👇
如果我们把 sessionId 改成别的,比如开头的 4 改为 3,就会显示未登录,因为没有找到👇
getSession 操作内部提取到请求中的Cookie 里的 SessionId, 然后根据SessionId获取到对应的Session 对象, Session 对象用HttpSession来描述
闲聊:我们理解一下上述过程
有的同学可能会迷糊,为啥代码都是一样的,它是怎么去区分的??我不同的客户拿过来不都是一样的吗??
我们这个 HttpSession 指的是和用户相关的,每一个用户都有一个 HttpSession 这样的对象
我的浏览器是一个用户
我的 Postman 又是一个用户
这两个用户在我们的服务端就会存储了2个 HttpSession 对象
我们去 getsession 的时候,就根据的 seesionId 获取的是当前用户的 Session,获取到这个 Session 对象之后我就可以去拿了
存储的时候也是同样的,为什么一个设置了 false,一个没有设置 false??
因为我们存储的话,那之前有值不就替换了嘛,之前没值咱就创建,如果是 false,我还需要去判断一下进行创建,那它之前有值就直接替换了
所以这里本身就是用户隔离的情况了
下面来看一下其他的 Session 获取方式
简洁获取 Session(2)
通过Spring MVC内置对象HttpSession 来获取
//获取session第二种方式 @RequestMapping("/getSession2") public String getSession(HttpSession session) { //从session中获取登录用户的信息 String userName=(String)session.getAttribute("userName"); return "登录用户为:"+userName; }HttpSession session = request.getSession(true);
Session 不存在的话, 会自动进行创建注意:重启服务后,服务器中的 Session 对象都没有了,所以根据 sessionId 是找不到的👇
所以我们需要先调用一次 setSession 👇
运行结果:http://127.0.0.1:8080/request/getSession2
简洁获取 Session(3)
通过注解 @SessionAttribute
//获取session第三种方式 @RequestMapping("/getSession3") public String getSession(@SessionAttribute("userName") String userName) { return "登录用户为:"+userName; }@SessionAttribute("userName") = (String)session.getAttribute("userName");
运行结果: http://127.0.0.1:8080/request/getSession3
我们会发现获取 session 的方式,一种比一种简单👇
2.4.11 获取 Header
传统获取 header
获取Header也是从HttpServletRequest 中获取
//获取Header第一种方式 @RequestMapping("/getHeader") public String getHeader(HttpServletRequest request) { String userAgent=request.getHeader("User-Agent"); return "从header中获取userAgent:"+userAgent; }使用HttpServletRequest 提供的getHeader方法来获取, 参数对应HTTP请求报头的"Key"
运行结果: http://127.0.0.1:8080/request/getHeader通过 Postman 获取👇
通过Fiddler观察, 获取的User-Agent是否正确
简洁获取 Header
//获取Header第二种方式 @RequestMapping("/getHeader2") public String getHeader2(@RequestHeader("User-Agent") String userAgent) { return "从header中获取userAgent:"+userAgent; }@RequestHeader("User-Agent") = request.getHeader("User-Agent");
@RequestHeader注解的参数值为HTTP请求报头中的"Key"
运行结果: http://127.0.0.1:8080/request/getHeader2,我们发现与上边是一样的👇使用 Postman 也是可以的,结果也是和第一种方法相同的👇
我们再使用其他浏览器试一下👇
我们发现这两种方式也是越来越简单的👇
其实如果你想拿到一些其他的信息,只要有的,都可以从这里面获取
大家也看到了,我们有一个万能公式,不管是 Cokkie 还是 Header,都可以通过 HttpServletRequest ,获取参数的话也是,HTTP 请求里面有啥,这个 HttpServletRequest 就有什么,所以我们之前前面学习的那些东西,都可以通过 HttpServletRequest 来获取的
闲聊:这些方式具体怎么用全凭个人喜好,到了公司之后老板喜欢用什么,咱就用什么~~
或者看领导和第一个创建项目的人,他咋用咱就咋用,因为大多数人其实都是复制粘贴的~~
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/Miraitowa_6/article/details/167702446















































































