【JavaEE08-后端部分】SpringMVC03-SpringMVC第二大核心处理请求之Cookie/Session和获取header

10 获取Cookie与Session【重点】

###认识Cookie和Session

HTTP 协议本身是 无状态 的------服务器记不住你是谁,下次访问就不认识你了。

为了让网站能记住"你是登录过的用户",就有了 CookieSession

咱们用去医院看病、复诊的场景,一次性讲明白。

一、先记一句最核心的话
  • Cookie = 你随身携带的「就诊卡」
  • Session = 医院系统里的「你的完整病历档案」

两者必须配合使用,缺一不可。


二、Cookie:你手里的「就诊卡」
1. 存在哪里?

存在你的客户端(浏览器/手机),由你自己带着。

2. 里面存什么?

只存一个编号 ,比如:JZ123456

就像就诊卡上只印个就诊号,不会写你的病情、病历、隐私

3. 特点
  • 体积很小,只能存少量信息
  • 每次去医院(访问网站),都会自动把卡交给医生(自动带给服务器)
4. 优点
  • 不占医院(服务器)存储空间
  • 自带、自动提交,使用方便
5. 缺点
  • 不安全:卡在你手里,可能被偷看、篡改
  • 容量小,存不了复杂内容
  • 绝对不能存密码、病情这类敏感信息

三、Session:医院里的「病历档案」
1. 存在哪里?

存在医院的服务器 里,由医院保管,不给你带走

2. 里面存什么?

你的全部信息

姓名、病情、诊断、用药、就诊记录、过敏史......

对应到网站就是:

登录状态、用户名、购物车、权限、用户数据等。

3. 特点
  • 你看不到、改不了医院里的档案
  • 必须通过就诊卡上的编号(Cookie 里的 SessionID),才能找到你的档案
4. 优点
  • 安全:敏感数据只存在服务器,用户碰不到
  • 容量大,可以存完整、复杂的用户数据
  • 最适合保存「登录状态」
5. 缺点
  • 占用服务器存储空间,用户越多,服务器压力越大
  • 必须依赖 Cookie,如果 Cookie 被禁用,Session 大概率也用不了

四、它们是怎么一起工作的?
  1. 你第一次去医院(第一次访问网站)
  2. 医院给你办一张就诊卡(Cookie),只写一个编号
  3. 医院系统里,根据这个编号,建一份你的病历(Session)
  4. 下次你再来,出示就诊卡(Cookie)
  5. 医院通过卡号,找到你的病历(Session)
  6. 医生立刻知道:哦,是你,我认得你

五、超简总结
  • Cookie 是卡,在你这,存编号,不安全、容量小
  • Session 是病历,在服务器,存完整信息,安全、容量大
  • Cookie 带编号,Session 存数据,一起实现"记住用户"

为什么使用cookie和session

我们之所以学习cookie和session是因为我们的HTTP是无状态的,而对于Java开发者来说,我们所开发的服务器基本都是HTTP服务器,所以如果HTTP无状态,那么我们的服务器就没有状态,那么所谓的无状态 就是没有记忆的意思,那么本质就是默认情况下HTTP协议的客⼾端和服务器之间的这次通信和下次通信之间没有直接的联系的。

但是很多时候是需要知道请求之间的关联关系的,怎么举一个🏥栗子:医院管理系统 · 从挂号到取报告

好的,我们从零开始,以医院管理系统 为例,一步步彻底拆解 "为什么通过 cookie 就能返回对应的就诊记录" 这个问题。

我会用最啰嗦、最细致的方式,确保你听完之后,再也没有疑惑。


🏥 医院管理系统 · 从挂号到取报告

📌 故事设定

  • 医院 :市立医院,有一个官网,患者可以登录系统查看自己的预约记录、检查报告、病历
  • 服务器:医院的一台中央服务器,运行着 Web 系统。
  • 患者:张大爷、李阿姨、王先生,他们互不认识,同时使用医院官网。

核心问题
张大爷登录后,点击"我的预约",为什么服务器返回的是张大爷的预约记录,而不是李阿姨或王先生的?

答案就在 Cookie 里。


第一章:HTTP 是无状态的------接待员的困境

1.1 什么是"无状态"?

想象医院大厅有一位接待员 ,他负责根据患者的就诊卡查找病历。

但这个接待员有个奇怪的设定
他每次接待完一个患者,就会把刚才的事忘得一干二净。下一患者过来,他完全不记得之前见过谁。

这就是 HTTP 协议的无状态性
服务器处理完一个请求后,立刻忘记这个请求和客户端的所有关联。下一个请求再来,它不认识你。

1.2 无状态带来的问题

张大爷第一次来医院,接待员帮他建了病历。

第二天张大爷又来,接待员完全不记得他 ,张大爷必须重新说一遍自己的姓名、身份证号......
这显然无法接受

所以,我们需要一种机制,让接待员(服务器)能"认出"回头患者。


第二章:解决方案------就诊卡和病历档案

医院设计了一套系统:

2.1 就诊卡(Cookie)

  • 就诊卡是一张塑料卡片 ,上面只印着一个编号 (比如 9527)。
  • 卡片由患者自己保管(存在浏览器里)。
  • 每次来医院,患者必须出示这张卡片(浏览器自动在请求里带上 Cookie)。

2.2 病历档案(Session)

  • 医院内部有一个巨大的病历档案柜(服务器内存中的 Session Map)。
  • 档案柜里按照就诊卡号分类存放病历(HttpSession 对象)。
  • 病历里详细记录着患者的姓名、身份证号、过敏史、预约记录等所有信息。
  • 病历由医院保管,患者看不到也改不了。

核心设计思想
就诊卡只带编号,病历存在医院。患者每次出示卡片,接待员根据编号找到病历,就知道你是谁,有什么病史。


第三章:患者第一次来访------挂号(登录)

3.1 张大爷第一次打开医院官网

操作 :张大爷在浏览器输入 www.hospital.com,回车。

浏览器发出的 HTTP 请求

复制代码
GET / HTTP/1.1
Host: www.hospital.com
...(没有 Cookie 字段)

注意 :请求头里没有 Cookie 。因为张大爷从来没访问过这个网站,浏览器里没有任何关于 hospital.com 的 Cookie。

服务器收到请求

  • 服务器检查请求中的 Cookie,发现没有
  • 服务器心想:这位患者没带就诊卡,可能只是来看看,我不需要为他创建病历(太早建病历会浪费内存)。
  • 服务器只返回医院官网首页的 HTML。

响应

复制代码
HTTP/1.1 200 OK
Content-Type: text/html

<html>...首页内容...</html>

响应头里没有 Set-Cookie,因为服务器没有创建任何 Session。


3.2 张大爷点击"登录"

操作 :张大爷在首页点击"登录",进入登录页面,输入用户名 zhangdage 和密码 123456,点击"登录"按钮。

浏览器发出 POST 请求

复制代码
POST /login HTTP/1.1
Host: www.hospital.com
Content-Type: application/x-www-form-urlencoded

username=zhangdage&password=123456

依然没有 Cookie(因为还没办卡)。


3.3 服务器处理登录------办卡 + 建档

医院服务器使用 SpringMVC,登录接口大致如下:

java 复制代码
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(String username, String password, HttpServletRequest request) {
    // 1. 验证用户名密码(假设调用数据库验证成功)
    if (checkUser(username, password)) {
        // 2. 获取用户ID(从数据库查)
        Long userId = getUserIdByUsername(username);  // 假设是 1001
        
        // 3. 【关键】获取 Session(没有则创建)
        HttpSession session = request.getSession();  // 相当于 request.getSession(true)
        
        // 4. 在 Session 中存入用户标识(病历里写名字)
        session.setAttribute("userId", userId);
        session.setAttribute("username", username);
        session.setAttribute("loginTime", new Date());
        
        // 5. 登录成功,跳转到患者个人主页
        return "redirect:/patient/home";
    } else {
        return "loginError";
    }
}

这一瞬间服务器内部发生了什么?------ 极其关键,请逐句理解

  1. 检查就诊卡request.getSession() 会先看当前请求有没有携带 Cookie,Cookie 里有没有 JSESSIONID

    • 张大爷的请求没有 Cookie ,所以服务器判定:这是一位新患者,没有就诊卡
  2. 创建新就诊卡号 :服务器调用 Session 管理器,生成一个全世界唯一的字符串 作为 SessionID。

    比如 ABC123XYZ(Tomcat 使用随机数 + 时间戳 + 进程ID + 计数器生成,几乎不可能重复)。

  3. 在病历档案柜中放入新病历

    Session Map 中增加一条记录:
    Key = "ABC123XYZ"
    Value = 一个新的 HttpSession 对象(可以看作一个 HashMap)。

  4. 在病历中填写患者信息
    session.setAttribute("userId", 1001);
    session.setAttribute("username", "zhangdage");

    ...

    这些数据就存在服务器内存的这个 HttpSession 对象里。

  5. 把就诊卡号发给患者

    服务器在 HTTP 响应头中添加:

    复制代码
    Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly

    这相当于告诉浏览器:"请保存这张就诊卡,卡号是 ABC123XYZ,以后每次访问医院网站,都要带上它。"

响应报文

复制代码
HTTP/1.1 302 Found
Location: /patient/home
Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly

3.4 浏览器收到就诊卡,妥善保管

张大爷的浏览器收到响应后,自动JSESSIONID=ABC123XYZ 这个键值对保存在 Cookie 存储区 ,关联的域名是 www.hospital.com

然后浏览器自动向 /patient/home 发起重定向请求。


第四章:患者复诊------出示就诊卡,找到病历

4.1 张大爷访问"个人主页"

浏览器自动发起 GET 请求(因为重定向):

复制代码
GET /patient/home HTTP/1.1
Host: www.hospital.com
Cookie: JSESSIONID=ABC123XYZ   ← 浏览器自动带上的就诊卡!

服务器处理(SpringMVC):

java 复制代码
@RequestMapping("/patient/home")
public String home(HttpSession session, Model model) {
    // Spring 自动根据请求中的 Cookie 找到 Session 对象
    Long userId = (Long) session.getAttribute("userId");
    String username = (String) session.getAttribute("username");
    
    // 将用户信息存入 Model,渲染个人主页
    model.addAttribute("username", username);
    return "patientHome";
}

服务器内部发生了什么?

  1. 读取就诊卡号 :从请求头 Cookie: JSESSIONID=ABC123XYZ 中提取出 ABC123XYZ
  2. 查找病历 :去 Session Map 里执行 map.get("ABC123XYZ")
    • 找到了!返回之前创建的 HttpSession 对象。
  3. 读取病历内容 :从该 Session 对象中取出 userIdusername 属性,得到 1001"zhangdage"
  4. 服务器确认:"哦,你是张大爷,你的病历在这里。"

响应:返回张大爷的个人主页,上面显示"欢迎,zhangdage"。


4.2 张大爷点击"我的预约记录"

操作:张大爷在个人主页点击"我的预约"链接。

浏览器发送请求(自动带 Cookie):

复制代码
GET /patient/appointments HTTP/1.1
Host: www.hospital.com
Cookie: JSESSIONID=ABC123XYZ

服务器处理

java 复制代码
@RequestMapping("/patient/appointments")
public String appointments(HttpSession session, Model model) {
    // 1. 从 Session 中取出 userId
    Long userId = (Long) session.getAttribute("userId");
    
    // 2. 根据 userId 查询数据库中的预约记录
    List<Appointment> list = appointmentService.findByUserId(userId);
    
    // 3. 存入 Model,返回页面
    model.addAttribute("appointments", list);
    return "appointmentList";
}

关键步骤

  • Session → userId :服务器通过就诊卡号(SessionID)找到了病历(HttpSession),从病历中拿到了患者的数据库主键 userId = 1001
  • userId → 预约记录 :拿着 1001 去数据库预约表中查询 WHERE user_id = 1001,得到张大爷的所有预约记录。
  • 返回记录:将这些记录渲染成 HTML 页面,发给浏览器。

响应:张大爷的浏览器上出现了他本人的三条预约记录:

  • 2月20日 骨科 张医生
  • 2月25日 内科 李医生
  • 3月1日 放射科 王医生

拆解成链条

  1. Cookie 里存的是 SessionID(就诊卡号)。
  2. SessionID 是服务器端病历档案柜的钥匙
  3. 服务器用这把钥匙打开对应患者的病历(HttpSession 对象)。
  4. 病历里存着登录时写入的用户唯一标识(如 userId)。
  5. 服务器用这个 userId 去数据库查询该用户的预约记录
  6. 数据库返回的记录自然只属于该用户
  7. 服务器把这些记录包装成响应,回到携带该 Cookie 的浏览器

所以,Cookie 本身并不直接"带回"记录,Cookie 只是带回了 SessionID,而 SessionID 让服务器能 找到病历 ,病历里存着用户身份,进而查到该用户的数据。


第五章:多个患者同时就诊------并发场景

5.1 李阿姨、王先生也登录了

就在张大爷登录的同时,李阿姨 (使用 Firefox)和王先生(使用手机 Chrome)也访问医院官网并登录。

  • 李阿姨登录
    • 服务器创建新 Session,生成 ID DEF456UVW
    • Session Map 新增:"DEF456UVW" → HttpSession( userId=1002, username="liaayi" )
    • 响应 Set-Cookie: JSESSIONID=DEF456UVW
  • 王先生登录
    • 服务器创建新 Session,生成 ID GHI789XYZ
    • Session Map 新增:"GHI789XYZ" → HttpSession( userId=1003, username="wangxiansheng" )
    • 响应 Set-Cookie: JSESSIONID=GHI789XYZ

此时服务器内存中的 Session Map 内容

复制代码
Key          Value (HttpSession 对象)
ABC123XYZ  → { userId: 1001, username: "zhangdage", ... }
DEF456UVW  → { userId: 1002, username: "liaayi", ... }
GHI789XYZ  → { userId: 1003, username: "wangxiansheng", ... }

5.2 三人同时点击"我的预约"

张大爷的请求

复制代码
GET /patient/appointments
Cookie: JSESSIONID=ABC123XYZ

李阿姨的请求

复制代码
GET /patient/appointments
Cookie: JSESSIONID=DEF456UVW

王先生的请求

复制代码
GET /patient/appointments
Cookie: JSESSIONID=GHI789XYZ

5.3 服务器并发处理

服务器(Tomcat)使用线程池同时处理三个请求,每个线程执行相同的代码:

java 复制代码
Long userId = (Long) session.getAttribute("userId");
List<Appointment> list = appointmentService.findByUserId(userId);

线程A(处理张大爷)

  • Cookie → ABC123XYZ → map.get("ABC123XYZ") → Session对象A → getAttribute("userId") = 1001 → 数据库查 userId=1001 → 返回张大爷的预约记录。

线程B(处理李阿姨)

  • Cookie → DEF456UVW → map.get("DEF456UVW") → Session对象B → userId=1002 → 数据库查李阿姨的记录。

线程C(处理王先生)

  • Cookie → GHI789XYZ → map.get("GHI789XYZ") → Session对象C → userId=1003 → 数据库查王先生的记录。

结果

  • 张大爷看到自己的记录。
  • 李阿姨看到自己的记录。
  • 王先生看到自己的记录。

互不干扰!

服务器虽然只写了一份代码,但通过不同的 Cookie(就诊卡号) 区分了不同患者,找到了不同的病历,取出了不同的 userId,查到了不同的数据库记录。


当服务器返回 Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly 时,浏览器做了三件事:

  1. 解析 Cookie 属性
    • 名称:JSESSIONID
    • 值:ABC123XYZ
    • 路径:/(表示整个网站下所有路径都携带这个 Cookie)
    • HttpOnly:禁止 JavaScript 读取该 Cookie(安全)
    • 没有 Max-Age 或 Expires → 会话级 Cookie,浏览器关闭即删除
  2. 存储 :浏览器将这个 Cookie 保存在与域名 www.hospital.com 关联的 Cookie 存储区中。
  3. 后续请求自动附加
    浏览器每次向 www.hospital.com 发送请求时,都会自动 检查该域名的 Cookie 存储区,找到所有符合路径要求的 Cookie,拼接到 Cookie 请求头中。

这就是为什么"只要登录过,后续请求就自动带上 SessionID"------全是浏览器自动完成的,前端开发者不需要写任何代码。


6.2 如果用户禁用了 Cookie?

如果用户在浏览器设置中禁用所有 Cookie,那么:

  • 服务器返回的 Set-Cookie 会被浏览器忽略。
  • 后续请求不会携带任何 Cookie。
  • 服务器每次调用 request.getSession() 都会因为没有 Cookie 而创建新的 Session,且无法将 SessionID 传给浏览器。
  • 结果:用户每刷新一次页面,服务器都认为是一个新用户,登录状态无法保持。

解决方案(传统方案):

  • URL 重写:response.encodeURL("/patient/appointments") 会在 URL 后自动添加 ;jsessionid=ABC123XYZ
  • 浏览器即使不支持 Cookie,也能通过 URL 传递 SessionID。
  • 缺点:URL 丑陋,容易意外分享导致会话劫持。

现代开发中,Cookie 几乎都是启用的,我们按默认情况讨论。


7.1 原始的方案:每次请求都带用户名密码

张大爷想看预约记录:

复制代码
GET /patient/appointments?username=zhangdage&password=123456

李阿姨:

复制代码
GET /patient/appointments?username=liaayi&password=abcdef

问题

  1. 极度不安全:用户名密码在 URL、历史记录、代理日志中明文可见。
  2. 每次都要验证密码:服务器每次都要查询数据库验证用户名密码,性能差(尤其高并发)。
  3. 无法存储临时状态:比如用户刚添加一个检查项目到"待预约列表",下次请求这个列表就没了,除非每次都把所有状态参数带回来,数据量巨大。
  4. 难以实现"记住我":每次都要输入密码。

7.2 Cookie+Session 的优势

  • 只传一次密码(登录时)。
  • 后续只传无意义的随机字符串(SessionID)
  • 服务器 O(1) 时间就能找到用户身份(哈希表查找,不查数据库)。
  • 临时状态(如购物车、表单草稿)可存在 Session 中,安全高效。

第八章:生命周期的细节------就诊卡和病历能存多久?

8.1 Cookie(就诊卡)的生命周期

我们之前看到的 Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly 没有设置 Max-Age 或 Expires

这种 Cookie 叫会话 Cookie ,保存在浏览器内存 中。
关闭浏览器标签或整个浏览器进程,该 Cookie 即被删除

效果:张大爷关闭浏览器,再打开访问医院网站,浏览器不会自动带任何 JSESSIONID Cookie。服务器会认为他是新患者,需要重新登录。

如果服务器在 Set-Cookie 中添加了 Max-Age=604800(7天):

复制代码
Set-Cookie: rememberMe=abc123; Max-Age=604800; Path=/; HttpOnly

这种 Cookie 会保存在硬盘上,7 天内即使重启浏览器,再次访问网站时浏览器依然会自动携带该 Cookie。

注意:持久 Cookie 通常不用于存储 SessionID(为了安全),而是用于存储"自动登录令牌",实现"记住我"功能。

8.2 Session(病历)的生命周期

创建

第一次调用 request.getSession(true) 时创建

在登录代码中,我们调用了 request.getSession(),所以登录成功瞬间创建了 Session。

存活

Session 默认存储在服务器内存中。

每个 Session 有一个最后访问时间 ,服务器有一个空闲超时时间(默认 30 分钟)。

  • 如果客户端 30 分钟内没有使用该 Session 发送任何请求,服务器会自动销毁该 Session。
  • 如果客户端一直有请求,每次请求都会刷新最后访问时间。

为什么要有超时?

如果不超时,已离开的用户 Session 会永远占用服务器内存,最终导致内存溢出。

销毁
  • 超时自动销毁
  • 主动销毁 :调用 session.invalidate()(如用户点击"注销")。
  • 服务器重启:内存 Session 会全部丢失(除非配置了持久化,如存 Redis)。
示例

张大爷登录后,30 分钟内没有任何操作,服务器自动删除 ABC123XYZ 这个 Session。

之后张大爷再点击链接,浏览器虽然还会带 Cookie: JSESSIONID=ABC123XYZ,但服务器去 Session Map 里 map.get("ABC123XYZ") 得到的是 null

服务器会认为:"这张就诊卡已经作废了,请重新挂号(登录)。"


第九章:底层实现------服务器到底是怎么找 Session 的?

9.1 Tomcat 中的 Session Map

在 Tomcat 中,Session 由 org.apache.catalina.session.ManagerBase 的子类管理(默认 StandardManager)。

它内部核心就是一个 ConcurrentHashMap<String, Session>

java 复制代码
protected Map<String, Session> sessions = new ConcurrentHashMap<>();
  • Key:String 类型的 SessionID。
  • ValueStandardSession 对象(实现了 HttpSession 接口)。

StandardSession 内部有一个 attributes 对象(也是一个 Map),存放我们通过 session.setAttribute() 存入的数据。

9.2 请求处理时如何获取 Session

SpringMVC 的处理流程(简化):

  1. DispatcherServlet 收到请求。

  2. 调用 RequestContextHolder 相关逻辑。

  3. 当 Controller 方法参数含有 HttpSession 时,Spring 会调用:

    java 复制代码
    HttpSession session = request.getSession(); // 无参 = true
  4. request.getSession() 最终由 org.apache.catalina.connector.Request 实现:

    • 从请求的 Cookie 中解析 JSESSIONID
    • 如果存在,调用 manager.findSession(jsessionid)sessions Map 中获取。
    • 如果找到,返回该 Session。
    • 如果没找到(或没有 Cookie),且参数为 true,则调用 manager.createSession() 生成新 SessionID、创建新 Session 对象、存入 Map,并生成 Set-Cookie 头。

9.3 数据库查询

我们在 Controller 中通过 session.getAttribute("userId") 拿到用户 ID,然后调用 Service 层:

java 复制代码
appointmentService.findByUserId(userId);

Service 层通过 MyBatis / JPA 执行 SQL:

sql 复制代码
SELECT * FROM appointments WHERE user_id = #{userId};

数据库返回该用户的所有预约记录。

整个过程的核心链条
Cookie(JSESSIONID)→ Session Map → HttpSession → userId → 数据库 → 预约记录


第十章:安全性------如何保护就诊卡不被盗用?

10.1 HttpOnly

Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly
HttpOnly 标志告诉浏览器:禁止 JavaScript 通过 document.cookie 读取这个 Cookie

  • 即使网站存在 XSS 漏洞,攻击者也无法窃取 JSESSIONID。
  • 必须设置

10.2 Secure

如果网站启用了 HTTPS,应添加 Secure 标志:

复制代码
Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly; Secure

这样 Cookie 只在 HTTPS 连接中传输,防止中间人攻击。

10.3 SameSite

用于防止 CSRF 攻击。可以设置为 LaxStrict

复制代码
Set-Cookie: JSESSIONID=ABC123XYZ; Path=/; HttpOnly; Secure; SameSite=Lax

10.4 定期更换 SessionID

登录成功后,可以调用 session.changeSessionId() 重新生成 SessionID,避免 Session Fixation 攻击。


第十一章:总结------一张图看懂整个流程

Set-Cookie: JSESSIONID=ABC123 是服务器通过 HTTP 响应头,告诉浏览器"请保存一个名为 JSESSIONID、值为 ABC123 的 Cookie"。

这个操作的本质就是服务器将 SessionID 传递给客户端,让客户端在后续请求中自动携带这个标识。


📮 具体含义拆解

  • 它是 HTTP 响应头中的一个字段,专门用于服务器向客户端(浏览器)发送 Cookie 指令。
  • 格式:Set-Cookie: <cookie-name>=<cookie-value>; 可选属性
2. 为什么叫 JSESSIONID
  • 这是 Java Web 容器(如 Tomcat)默认的 Session Cookie 名称。
  • 当服务器创建了一个新的 HttpSession 对象时,会自动生成一个唯一的 SessionID,并将它放在名为 JSESSIONID 的 Cookie 中返回给浏览器。
  • 解析这个响应头,获取 Cookie 的名称、值以及可选属性(如 PathHttpOnlyMax-Age 等)。
  • 将该 Cookie 保存在与当前网站(域名)对应的 Cookie 存储中。
  • 以后每次向该域名发送请求,浏览器都会自动 在请求头中加入 Cookie: JSESSIONID=ABC123(只要符合路径等规则)。

因为 cookie 里存的是就诊卡号(SessionID),这张卡号能帮服务器在病历档案柜(Session Map)里找到属于该患者的病历(HttpSession)。病历里记录了患者的身份标识(userId),服务器拿着这个标识去数据库查询,得到的就是该患者本人的预约记录。

没有 cookie,服务器就不知道你拿的是哪份病历;没有 Session,cookie 就只是一串无意义的数字。两者配合,才能实现"记住你是谁,并给你看专属内容"。

第十二章 补充和细节

简单理解

"举个简单的例子,假如有5个客户端同时访问服务器,那么服务器会为这5个客户端分别生成唯一的Session ID。这些Session ID作为后端Session存储结构中的key,每个key对应一个value(即Session对象)。当客户端(前端)再次访问服务器时,浏览器会自动携带这个Session ID(通过Cookie)。后端服务器接收到请求后,根据这个Session ID,在一个类似哈希表的Map集合中查找对应的Session对象,从而识别出该用户是谁。基于识别出的用户身份,服务器就可以为该用户赋予相应的权限和功能。"

他对应简图如下:

🧠 为什么 Session 默认放内存?

  • 速度快:内存读写是纳秒级,比硬盘、数据库快得多。
  • 实现简单:Web 容器(如 Tomcat)内置了内存中的 Session 管理器,开箱即用。

💥 重启服务器后发生了什么?

假设我们的医院系统有 10 个患者在线,服务器内存中的 Session Map 里有 10 份病历。

突然医院停电,服务器重启------内存里的所有病历瞬间消失

但是!患者手里的就诊卡(Cookie 中的 JSESSIONID)并没有消失 (除非是会话级 Cookie 且浏览器关闭了)。

重启后,患者继续操作,浏览器依然会自动带上 JSESSIONID=xxx

服务器收到请求后,拿着这个 ID 去内存的 Session Map 里查找------找不到 (因为 Map 是空的)。

于是服务器认为这是一张无效的就诊卡,通常会跳转到登录页,要求患者重新登录(也就是重新建档)。

这就是为什么生产环境中的服务器不能随便重启,否则所有在线用户都会被踢下线


🛠️ 如何解决重启丢失 Session 的问题?

1. Session 持久化到硬盘(不推荐)

Tomcat 支持将 Session 定期保存到硬盘文件,重启时再加载。

但性能差,且多服务器集群时无法共享。

2. 使用外部存储 ------ 主流方案

将 Session 数据存到 Redis 这样的内存数据库。

  • Redis 本身也基于内存,但支持持久化到硬盘,重启 Redis 可恢复。
  • 多台应用服务器共享同一个 Redis,Session 不丢失,且支持水平扩展。

Spring Session 项目可以轻松地将 Session 存储切换到 Redis,代码几乎无改动。

3. 客户端存储方案(如 JWT)

不再依赖服务器端 Session,用户状态加密后存在客户端(如 JWT),每次请求携带。

服务器无状态,重启也不影响。但需要处理令牌刷新、注销等复杂逻辑。


Ok,读完上传的内容,想必老铁们已经知道cookie和session到底是什么东西了,那我们后端如何去获取cookie和session呢?接下来我们就一一讲述。


获取Cookie

传统方式:通过HttpServletRequest的getCookies()方法获取所有Cookie。

认识HttpServletRequest和HttpServletResponse

如图所示,我们的HttpServletRequest就是HTTP请求,HttpServletResponse就是HTTP响应。

那么这两个类中封装的是什么?

比如有以下的访问:


HttpServletRequest封装的东西如下:

如果你想要后端从HTTP请求中获取到什么就可以从HttpServletRequest对象中去拿。【所以这个类中多是get方法】
HttpServletRequest(读取HTTP请求)

HTTP对应部分 方法签名 方法说明 使用场景&示例调用 优先级
【请求行】 String getMethod() 获取HTTP请求方法(GET/POST/PUT/DELETE/OPTIONS等) 判断请求类型:if (request.getMethod().equals("POST")) {...} 高频
【请求行】 StringBuffer getRequestURL() 获取完整请求URL(含协议、域名、端口、路径,无查询参数) 日志记录:String fullUrl = request.getRequestURL().toString(); 常用
【请求行】 String getRequestURI() 获取请求URI(仅路径,不含域名/端口/查询参数) 权限拦截:String uri = request.getRequestURI(); // /http-demo/read 高频
【请求行】 String getQueryString() 获取URL后的查询字符串(?name=张三&age=20) 手动解析参数:String query = request.getQueryString(); 常用
【请求行】 String getProtocol() 获取HTTP协议版本(HTTP/1.1、HTTP/2) 协议适配:String protocol = request.getProtocol(); 进阶
【请求头】 String getHeader(String name) 获取指定名称的请求头(大小写不敏感) 取Token:String token = request.getHeader("Authorization"); 高频
【请求头】 Enumeration<String> getHeaderNames() 获取所有请求头名称 遍历所有请求头:Enumeration<String> names = request.getHeaderNames(); 常用
【请求头】 Enumeration<String> getHeaders(String name) 获取多值请求头(如Accept-Encoding) 取多值头:Enumeration<String> encodings = request.getHeaders("Accept-Encoding"); 进阶
【请求头】 int getIntHeader(String name) 获取数值型请求头(如Content-Length) 取内容长度:int length = request.getIntHeader("Content-Length"); 进阶
【请求头】 long getDateHeader(String name) 获取日期型请求头(如If-Modified-Since) 缓存校验:long modifyTime = request.getDateHeader("If-Modified-Since"); 进阶
【请求参数】 String getParameter(String name) 获取单个请求参数(GET/POST表单通用) 取表单参数:String username = request.getParameter("username"); 高频
【请求参数】 String[] getParameterValues(String name) 获取多值参数(如复选框) 取爱好:String[] hobbies = request.getParameterValues("hobby"); 高频
【请求参数】 Map<String, String[]> getParameterMap() 获取所有参数的键值对Map 批量解析参数:Map<String, String[]> paramMap = request.getParameterMap(); 常用
【请求参数】 Enumeration<String> getParameterNames() 获取所有参数名称 遍历参数:Enumeration<String> paramNames = request.getParameterNames(); 进阶
【Cookie】 Cookie[] getCookies() 获取客户端携带的所有Cookie数组 取登录Cookie:Cookie[] cookies = request.getCookies(); 高频
【请求体】 BufferedReader getReader() 获取字符流,读取文本请求体(JSON/XML/表单) 手动读JSON:BufferedReader reader = request.getReader(); 常用
【请求体】 ServletInputStream getInputStream() 获取字节流,读取二进制请求体(文件/图片) 接收文件:ServletInputStream is = request.getInputStream(); 进阶
【请求体/编码】 String getCharacterEncoding() 获取请求体的编码格式(如UTF-8、ISO-8859-1) 校验编码:String encoding = request.getCharacterEncoding(); 常用
【请求体/编码】 void setCharacterEncoding(String env) 设置请求体编码(解决POST中文乱码) 防乱码:request.setCharacterEncoding("UTF-8");(必须在取参数前调用) 高频
【会话Session】 HttpSession getSession() 获取当前会话(无则创建) 存会话数据:HttpSession session = request.getSession(); session.setAttribute("user", "admin"); 高频
【会话Session】 HttpSession getSession(boolean create) 获取会话(create=false则无会话返回null) 检查会话:HttpSession session = request.getSession(false); 常用
【会话Session】 String getRequestedSessionId() 获取请求中的会话ID(JSESSIONID) 校验会话:String sessionId = request.getRequestedSessionId(); 进阶
【客户端信息】 String getRemoteAddr() 获取客户端IP地址(如127.0.0.1、192.168.1.100) 记录IP/限制访问:String clientIp = request.getRemoteAddr(); 高频
【客户端信息】 int getRemotePort() 获取客户端请求的端口号 日志记录:int port = request.getRemotePort(); 进阶
【客户端信息】 String getRemoteHost() 获取客户端主机名(需DNS解析,默认返回IP) 高级日志:String host = request.getRemoteHost(); 进阶
【服务器信息】 String getLocalAddr() 获取服务器接收请求的IP地址 多网卡适配:String serverIp = request.getLocalAddr(); 进阶
【服务器信息】 int getLocalPort() 获取服务器接收请求的端口号 端口校验:int serverPort = request.getLocalPort(); 进阶
【路径信息】 String getContextPath() 获取项目上下文路径(SpringBoot默认空字符串,WAR包部署为项目名) 拼接路径:String context = request.getContextPath(); 常用
【路径信息】 String getServletPath() 获取Servlet映射路径(Controller的@RequestMapping路径) 路径匹配:String servletPath = request.getServletPath(); 进阶
【内容信息】 int getContentLength() 获取请求体长度(字节数) 大小限制:int length = request.getContentLength(); 进阶
【内容信息】 long getContentLengthLong() 获取大请求体长度(long型,避免溢出) 大文件校验:long longLength = request.getContentLengthLong(); 进阶
【内容信息】 String getContentType() 获取请求体的Content-Type(如application/json、multipart/form-data) 判断请求类型:String contentType = request.getContentType(); 常用

HttpServletResponse封装的东西如下:

如果你想要后端从HTTP响应中返回什么都可以从HttpServletResponse对象中去设置。

HttpServletResponse(设置HTTP响应)

HTTP对应部分 方法签名 方法说明 使用场景&示例调用 优先级
【响应行-状态码】 void setStatus(int sc) 设置成功/重定向状态码(200/301/302) 正常响应:response.setStatus(200);(默认,可省略) 常用
【响应行-状态码】 void sendError(int sc) 发送错误状态码(404/500),附带默认提示信息 资源不存在:response.sendError(404); 常用
【响应行-状态码】 void sendError(int sc, String msg) 发送错误状态码+自定义提示信息 权限不足:response.sendError(403, "无访问权限"); 高频
【响应头】 void setHeader(String name, String value) 设置响应头(覆盖同名头) 禁止缓存:response.setHeader("Cache-Control", "no-cache"); 高频
【响应头】 void addHeader(String name, String value) 追加响应头(不覆盖同名头) 多值头:response.addHeader("Allow", "GET"); response.addHeader("Allow", "POST"); 进阶
【响应头】 void setIntHeader(String name, int value) 设置数值型响应头 内容长度:response.setIntHeader("Content-Length", 1024); 进阶
【响应头】 void addIntHeader(String name, int value) 追加数值型响应头 超时时间:response.addIntHeader("Timeout", 30); 进阶
【响应头】 void setDateHeader(String name, long date) 设置日期型响应头(毫秒数) 缓存过期:response.setDateHeader("Expires", System.currentTimeMillis() + 3600000); 进阶
【响应头】 void addDateHeader(String name, long date) 追加日期型响应头 多时间戳:response.addDateHeader("Last-Modified", System.currentTimeMillis()); 进阶
【Cookie】 void addCookie(Cookie cookie) 向响应中添加Cookie(生成Set-Cookie响应头) 设置Token:Cookie cookie = new Cookie("token", "abc123"); response.addCookie(cookie); 高频
【响应体-编码】 void setCharacterEncoding(String charset) 设置响应体编码(如UTF-8) 防乱码:response.setCharacterEncoding("UTF-8"); 高频
【响应体-编码】 String getCharacterEncoding() 获取响应体当前编码 校验编码:String encoding = response.getCharacterEncoding(); 进阶
【响应体-类型】 void setContentType(String type) 设置响应体Content-Type(含编码,推荐用这个) 返回JSON:response.setContentType("application/json;charset=UTF-8"); 高频
【响应体-类型】 String getContentType() 获取响应体Content-Type 校验类型:String contentType = response.getContentType(); 进阶
【响应体-文本】 PrintWriter getWriter() 获取字符输出流,写入文本响应体(JSON/HTML/纯文本) 返回JSON:response.getWriter().write("{\"code\":200}"); 高频
【响应体-二进制】 ServletOutputStream getOutputStream() 获取字节输出流,写入二进制响应体(图片/文件) 返回图片:ServletOutputStream os = response.getOutputStream(); 常用
【重定向】 void sendRedirect(String location) 发送302重定向(自动设置Location头) 跳转到首页:response.sendRedirect("/index"); 高频
【内容信息】 void setContentLength(int len) 设置响应体长度(字节数) 固定长度响应:response.setContentLength(1024); 进阶
【内容信息】 void setContentLengthLong(long len) 设置大响应体长度(long型) 大文件下载:response.setContentLengthLong(1024*1024*10); 进阶
【缓存控制】 void setBufferSize(int size) 设置响应缓冲区大小 大响应优化:response.setBufferSize(8192); 进阶
【缓存控制】 void flushBuffer() 刷新响应缓冲区(强制输出内容) 实时输出:response.flushBuffer(); 进阶
【缓存控制】 boolean isCommitted() 判断响应是否已提交(提交后无法修改头/状态码) 防重复响应:if (!response.isCommitted()) { response.sendRedirect("/"); } 常用
【其他】 void reset() 重置响应(清空头、状态码、缓冲区,仅未提交时可用) 响应重置:if (!response.isCommitted()) { response.reset(); } 进阶
【其他】 void resetBuffer() 仅重置缓冲区(保留头和状态码) 缓冲区清理:response.resetBuffer(); 进阶

使用传统方式HttpServletRequest中的getCookies()方法获取所有的cookies

1)Postman中如何添加cookie


2)Cookie 本质就是绑定域名(Domain)的,它不属于某个接口,属于某个域名。所以我们首先就得添加域名domain,比如你添加了127.0.0.1那么后面你的这个cookie都会对其生效。


3)然后点击添加cookie,我们可以添加多个


4)后端的代码

java 复制代码
  /**
     * 使用传统方式获取Cookie
     * @param request 内置参数
     * @return 成功就返回成功
     */
    @RequestMapping("/r12")
    public String r12(HttpServletRequest request) {
        //首先我直接调用方法获取到cookies数组
        Cookie[] cookies = request.getCookies();

        //那么接下来就是遍历cookie数组了
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                //这里边就分别将键key和值value给打印出来
                //所以我们就分别调用不同的方法
                System.out.println("Cookie的键:" + cookie.getName() + ", "
                + "Cookie的值:" + cookie.getValue());
            }
        }

        return "获取成功!!!";
    }

5)前端的请求


6)结果


7)🤔:我们为什么可以从HTTP请求中获取到cookie?

因为前面在认识Cookie和Session的时候,我们就说过,后端生成的SessionID存在cookie中,由服务器返回给前端,前端浏览器将cookie保存,并且每一次请求都会自动将Cookie放到HTTP请求头中带到后端,而我们HTTP请求被抽象为HttpServletRequest类,所以我们才可以从该对象中获取到Cookie。


简洁方式:使用@CookieValue注解直接获取指定名称的Cookie值。

那么这个方式的特点就是说,一个@CookieValue注解只能获取一个cookie的值,而且我们都是通过键key来得到对应的值的

java 复制代码
// 简洁方式:获取名称为bite的Cookie值
@RequestMapping("/getCookie")
public String cookie(@CookieValue("bite") String bite) {
    return "bite:" + bite;
}

1)我们再添加一个Cookie sid = 1001


有多少cookie,只要你的cookie都是针对同一个域名,那么我们每一次请求都会带上多少Cookie


2)后端代码

java 复制代码
**
     * 使用简洁方式获取Cookie
     * @param sid
     * @return
     */
    @RequestMapping("/r13")
    public String r13(@CookieValue("sid") String sid) {
        return "从cookie中获取到sid的值是:" + sid;
    }

3)前端请求及结果


传统方式和简洁方式的选择

两种方式我们推荐使用方式一,因为方式二有一个缺点就是说一个注解只能获取一个cookie,我们实际开发中往往会有多个Cookie,如果使用方式二,那么就得使用多次注解,关键你要知道Cookie的键key是什么,根据key来得到value。


获取Session

Session需先存储,再获取,常用方式有3种:

  1. 通过HttpServletRequest的getSession()方法存储和获取(getSession(true):不存在则创建;getSession(false):不存在则返回null)。
  2. 使用@SessionAttribute注解直接获取Session中的值。
  3. 直接将HttpSession作为方法参数,获取Session对象。

看完这三种获取session对象的方式,你会不会有一个疑问?

我们获取cookie,我们是从HTTP请求中去获取的。然后这个HTTP请求呢被抽象成了一个HttpServletRequest对象,我们能从中获取cookie,是因为浏览器每次请求会把这个cookie自动带上,所以呢我们直接从请求中获取进行了。而对于这里获取session它有三种方法,尤其是第一种,通过HttpServletRequest中的getSession()方法来获取,很多初学者就有这样的疑问,我们的session对象不是存在后端的吗?那么你为什么会把它封装在我们的请求中去呢?

其实这样的一个核心疑问是正常的,但是我们并不是把session对象封装到请求中,而是请求中有一个唯一和session有关的东西叫做sessionID,这个session id是存在cookie中的,我们可以通过这个getSession()方法来获取这个session ID,从而通过session id去后端获取到我们的session对象。
那么之所以session对象是存在我们后端的,所以呢筛选对象,我们用HttpSession这个类来抽象出来。那么自然上述三种方法最终底层里面得到的那个session都是和HttpSession类的实例。

这样大概的解释可能会有点模糊,我们具体看一下的解释。


产生疑问的核心矛盾是:Session本体确实在后端,但请求里带的不是Session,而是"找到这个Session的钥匙(SessionID)"。我们先把这个核心逻辑讲透,再拆解三种方式的区别,老铁们应该马上就懂了。

一、先解决核心困惑:Session在后端,为什么和请求有关?

用一个通俗的比喻:

  • 后端服务器 = 酒店
  • Session = 你在酒店开的房间(本体在酒店,不在你身上)
  • SessionID(JSESSIONID) = 你的房卡(一串字符串,存在Cookie里)
  • 前端请求 = 你拿着房卡去酒店找房间

完整流程:

  1. 你第一次请求酒店(后端),酒店没你的房间(Session),就给你开一间(创建Session),并把房卡(SessionID)给你(通过Cookie返回);
  2. 你后续每次来酒店(发请求),都会带着房卡(SessionID,在HTTP请求头的Cookie里)
  3. 酒店(后端)拿到你的房卡(从HttpServletRequest里取Cookie中的SessionID),就能找到对应的房间(Session);
  4. 房间(Session)始终在酒店(后端),你带的只是房卡(SessionID),不是房间本身。

对应技术逻辑:

  • request.getSession() 做的事:
    ① 从请求头的Cookie里读JSESSIONID(这是请求里唯一和Session相关的东西);
    ② 拿着这个ID去后端的Session容器里找对应的HttpSession对象;
    ③ 如果找到,返回这个Session;如果没找到且参数是true,就创建新Session并生成新的JSESSIONID,通过响应头的Set-Cookie返回给前端。

这就是为什么"Session在后端,但HttpServletRequest能操作Session"------因为请求里带了"找Session的钥匙(SessionID)",getSession()本质是"用钥匙找后端的Session"。


二、三种操作Session方式的底层逻辑+区别

先上核心结论:三种方式最终操作的都是同一个后端的HttpSession对象,只是"获取Session的入口不同",适用场景不同

1. HttpServletRequest.getSession()(最原生,万能)

  • 底层逻辑
    ① 从请求的Cookie中提取JSESSIONID;
    ② 用ID匹配后端的HttpSession;
    ③ 按需创建/返回Session(true创建,false不创建)。
  • 代码示例
java 复制代码
@GetMapping("/session1")
public String testSession1(HttpServletRequest request) {
    // 1. 获取Session(不存在则创建)
    HttpSession session = request.getSession(true); // 等价于request.getSession()
    // 2. 存值
    session.setAttribute("username", "张三");
    // 3. 取值
    String username = (String) session.getAttribute("username");
    // 4. 若想不创建Session,只查询:
    HttpSession session2 = request.getSession(false);
    if (session2 == null) {
        return "暂无Session";
    }
    return "用户名:" + username;
}
  • 特点
    ✅ 能控制"是否创建新Session"(true/false),灵活性最高;
    ✅ 是所有Session操作的"底层入口"(后面两种方式最终都依赖这个逻辑);
    ❌ 代码稍繁琐,需要手动强转属性类型。

2. 直接将HttpSession作为方法参数(SpringBoot简化版)

  • 底层逻辑
    SpringBoot自动帮你做了"request.getSession(true)"的操作------即:
    ① 从request里提取SessionID;
    ② 找到/创建Session;
    ③ 直接把Session对象注入到方法参数里。
  • 代码示例
java 复制代码
@GetMapping("/session2")
public String testSession2(HttpSession session) {
    // 直接用注入的Session(默认是getSession(true),不存在则创建)
    session.setAttribute("age", 20);
    Integer age = (Integer) session.getAttribute("age");
    return "年龄:" + age;
}
  • 特点
    ✅ 代码更简洁,少了request.getSession()这一步;
    ❌ 无法控制"不创建Session"------只要你把HttpSession作为参数,SpringBoot就会自动调用getSession(true),哪怕原本没有Session,也会创建新的;
    ❌ 仍需手动强转属性类型。

3. @SessionAttribute注解(SpringMVC特化版,取值专用)

  • 底层逻辑
    SpringMVC帮你做了"获取Session + 提取属性"的两步操作------直接从当前Session中提取指定名称的属性,无需手动调用getAttribute()
  • 代码示例
java 复制代码
@GetMapping("/session3")
public String testSession3(
        // 从Session中提取key为"username"的值,赋值给参数username
        @SessionAttribute(value = "username", required = false) String username,
        // 提取key为"age"的值,required=true(默认),若不存在会抛异常
        @SessionAttribute("age") Integer age
) {
    // 直接用参数,无需手动取值
    return "用户名:" + username + ",年龄:" + age;
}
  • 特点
    ✅ 取值极简洁,无需手动调用getAttribute()和强转;
    ❌ 只能取值 ,不能存值/删值(存/删仍需用HttpSession对象);
    ❌ 可通过required=false避免属性不存在时抛异常;
    ❌ 本质还是依赖request.getSession(true),若没有Session会先创建。

三、核心区别总结(表格版,一目了然)

方式 核心入口 是否能控制创建Session 支持操作(存/取/删) 代码简洁度 适用场景
request.getSession() HttpServletRequest 能(true/false) 全支持 一般 需要灵活控制Session创建(如登录校验)
HttpSession作为方法参数 直接注入HttpSession 不能(默认创建) 全支持 较高 常规的Session存/取(无需控制创建)
@SessionAttribute注解 直接提取Session属性 不能(默认创建) 仅取值 最高 仅需读取Session中的某个属性

四、补充关键提醒

  1. 三种方式的"底层本质":最终都依赖"请求里的JSESSIONID"找到后端的HttpSession,只是SpringBoot帮你简化了代码;
  2. 性能/功能无差异:操作的是同一个Session对象,不存在"谁更快"的问题;
  3. 避坑点:
    • 若想"仅查询Session,不创建",只能用request.getSession(false),另外两种方式都会自动创建Session;
    • @SessionAttribute的required默认是true,若Session中无该属性会抛异常,建议按需设为false。

老铁们现在可以这么记:

  • 要"灵活控制Session是否创建" → 用request.getSession()
  • 常规存/取,不想写多余代码 → 用HttpSession参数;
  • 只需要读某个Session属性 → 用@SessionAttribute。

具体例子

上述解释完三种方式区别之后,我们这里就开始具体的实操一下。

获取session第一种方式

咱们来一步一步的看:我们以登录案例来看

1)首先我们回顾一下之前的HTTP请求逻辑图:


2)那么我们的Session对象是存在服务器端的,所以我们登录的话,就得创建Session对象,然后将用户信息存在里面,看下述代码:

java 复制代码
    /**
     * 模拟用户第一次登陆存储用户的信息:存储Session
     * @param request 用于获取请求中的SessionID
     */
    @GetMapping("/setSession")
    public String r14(HttpServletRequest request) {
        //首先第一步就是从我们的请求中午获取Cookie,再从cookie中获取SessionID
        //再根据SessionID去找我们的Session对象,看是否存在这个对象
        HttpSession session = request.getSession();
        
        return "成功";
    }

这里面值得注意的是:request.getSession()方法,为什么呢?这里是重载,这个方法有两个,如图:

  • 首先没有参数的HttpSession getSession();默认是true,那么参数为true,我们前面说过他底层的原理就是根据SessionID去找有没有对应的Session对象,有就将对象返回。如果没有就会创建一个Session对象并返回【会生成一个唯一的 SessionID(如JSESSIONID=88F9E7D234567890),同时通过响应头Set-Cookie: JSESSIONID=xxx; Path=/把这个 SessionID 传给客户端(浏览器),浏览器会把这个 Cookie 保存下来。】
  • 其次有参数的 HttpSession getSession(boolean var1);指定参数为true的作用和无参的一样,而指定参数为false,即 request.getSession(false);他底层的原理就是根据sessionId去找一下看有没有对应的Session对象,如果有的话就将该对象返回,如果没有的话就不会创建Session对象而是直接返回null。

3)那么既然我们写的是用户未登录而需要登录的逻辑,此时是没有Session对象的,所以此时我们就是用无参的方法,并且将对应的用户信息存储起来,对应的代码逻辑如下所示:

java 复制代码
  /**
     * 模拟用户第一次登陆存储用户的信息:存储Session
     * @param request 用于获取请求中的SessionID
     */
    @GetMapping("/setSession")
    public String r14(HttpServletRequest request) {
        //首先第一步就是从我们的请求中午获取Cookie,再从cookie中获取SessionID
        //再根据SessionID去找我们的Session对象,看是否存在这个对象
        HttpSession session = request.getSession();


        //此时由于用户登陆成功,那么我们就会在底层创建出Session对象
        //并且将用户信息存储在Session对象之中
        session.setAttribute("name", "张三");
        session.setAttribute("age", "18");
        return "Session设置成功";
    }

4)那么咱们将存储Session对象的代码写完毕之后,我们就来写获取Session的代码,此时注意的是,咱们获取Session的主要就是获取Session对象,那么如果你的Session对象不存在我们是不需要登录的,所以使用有参方法,将参数设置为falserequest.getSession(false),对应的代码如下:

java 复制代码
  /**
     * 模拟用户登录成功之后获取用户的信息:获取Session
     * @param request 用于获取请求中的SessionID
     */
    @GetMapping("/getSession")
    public String r15(HttpServletRequest request) {
        //首先第一步就是从我们的请求中午获取Cookie,再从cookie中获取SessionID
        //再根据SessionID去找我们的Session对象,看是否存在这个对象
        //如果不存在就直接返回null
        HttpSession session = request.getSession(false);//加上false这个参数代表的是没有Session对象我不会自动创建

        if (session == null) {
            return "用户未登录!!";
        } else {
            //程序走到这里就代表的是用户已经登录了,他已经有了对应的Session对象
            //此时我们就从Session中获取对应的用户信息
            String name = (String) session.getAttribute("name");
            String age = (String) session.getAttribute("age");

            return "获取Session中的用户信息为:" + name + age + "岁~";
        }
    }

5)此时咱们来测试存储Session:

首先使用浏览器访问,然后观察到我们对应的请求中没有cookie

返回的时候响应中带回对应的Cookie,里面有对应的SessionID

我们的浏览器将Cookie存起来


6)此时测试获取Session

我们使用浏览器访问,那么此时请求中自动带有Cookie,里面有对应的SessionID,后端根据他找到对应的Session对象并将用户信息返回。

请求中自动带有cookie

后端根据cookie中的Sessionid获取到给对象,此时就对应的结果返回


7)那么同样的道理,不同的软件发起的请求就是不同的客户端,使用Postman请求的时候后端也会产生唯一的SessionID,也会被Postman存起来,下一次请求自动带上。

发送请求

一旦请求成功之后,我们的Postman就会将SessionID给自动设置到Cookie中

同时我们也可以看对应的sessionID

此时返回后端获取session接口:


8)那么上述如果我们修改一下cookie中的sessionID的值

此时结果就是未登录状态


9)那么上述session对象简单的内存图如下所示:


10)我们不同的客户端,对应不同的session对象用于标识,那么到时候,我们设置session对象中的数据的时候,就可以动态设置了


获取session第二种方式HttpSession

HttpSession session 等价于 HttpSession session = request.getSession(true);

java 复制代码
   /**
     * 获取session的第二种方式:HttpSession
     */
    @RequestMapping("/getSession2")
    public String getSession2(HttpSession session) {

        String name = (String) session.getAttribute("name");
        String age = (String) session.getAttribute("age");

        return "获取Session的第二种方式为:" + name + age + "岁~";
    }

获取session第三种方式@SessionAttribute

@SessionAttribute(参数)等价于 session.getAttribute(参数)

java 复制代码
   /**
     * 获取session的第三种方式:@SessionAttribute
     */
    @RequestMapping("/getSession3")
    public String getSession3(@SessionAttribute("name") String name,
                              @SessionAttribute("age") String age) {
        
        
        return "获取Session的第三种方式为:" + name + age + "岁~";
    }

总结三种方式

三种方式的特点就是一种比一种简单,具体看下图:


11 获取请求头(@RequestHeader)

用于获取HTTP请求头中的信息,有两种方式:

  • 传统方式:通过HttpServletRequest的getHeader()方法获取。
  • 简洁方式:使用@RequestHeader注解直接获取指定名称的请求头值。

作用:获取 Header 主要用于鉴权、客户端适配、来源校验、链路追踪等场景,是 Web 开发中识别请求上下文的关键手段。


比如我们想看访问该服务器的客户端是什么,此时就可以拿到HTTP请求中请求头中的属性User-Agent中的值来观察,具体例子我在下述代码中看。


传统方式:通过HttpServletRequest的getHeader()方法获取。

java 复制代码
    //获取请求头header方式1
    @RequestMapping("/getHeader1")
    public String getHeader1(HttpServletRequest request) {
        String ua = request.getHeader("User-Agent");
        return "方式1得出使用的客户端是:" + ua;
    }

简洁方式:使用@RequestHeader注解直接获取指定名称的请求头值。

java 复制代码
 //获取请求头header方式2
    @RequestMapping("/getHeader2")
    public String getHeader2(@RequestHeader("User-Agent") String ua) {
        return "方式2得出使用的客户端是:" + ua;
    }

@RequestHeader("User-Agent") String ua 等价于 String ua = request.getHeader("User-Agent");


总结

也是一样的,代码一次比一次简单


✅ SpringMVC 核心二「处理请求」完美收官!

📌 重点划重点:Cookie & Session

🔜 下期预告:核心三「处理响应」

最后老铁们,点赞关注不迷路,我们下期见~~!

相关推荐
长路 ㅤ   1 小时前
02、Langchain4j tools原理与核心实践(含自定义http插件)
spring boot·langchain4j·ai工具调用·java ai开发
2501_926978331 小时前
分形时空理论框架:从破缺悖论到意识宇宙的物理学新范式引言(理论概念版)--AGI理论系统基础1.1
java·服务器·前端·人工智能·经验分享·agi
西门吹雪分身1 小时前
K8S之Pod调度
java·容器·kubernetes·k8s
追随者永远是胜利者1 小时前
(LeetCode-Hot100)10. 正则表达式匹配
java·算法·leetcode·go
追随者永远是胜利者2 小时前
(LeetCode-Hot100)17. 电话号码的字母组合
java·算法·leetcode·职场和发展·go
独自破碎E2 小时前
BISHI53 [P1080] 国王游戏(简化版)
android·java·游戏
坚持就完事了2 小时前
Java中的异常
java·开发语言
~央千澈~2 小时前
抖音弹幕游戏开发之第11集:礼物触发功能·优雅草云桧·卓伊凡
java·前端·python
wuqingshun3141592 小时前
说一下HashMap和HashTable的区别
java·开发语言