你好!作为新手小白,面对代码和复杂的概念感到困惑是非常正常的。Spring Web MVC 是 Java Web 开发中非常核心的框架,而你提供的资料主要涵盖了 Web 开发中最关键的一个环节------"会话管理"(即如何让服务器记住你是谁)。
这份资料通过"医院挂号"等生动的例子,详细讲解了 Cookie 和 Session 的机制。
为了让你能够透彻理解,我将这份教程分成了几个部分:核心概念解析 、详细代码拆解 、完整代码展示 以及扩展知识点。我会用最通俗易懂的语言,配合代码中的逐行注释,带你一步步掌握这些内容。
我们先来通过一个文件,把资料中所有零散的代码整合在一个完整的类中,方便你直观地看到它们是如何在 Spring Boot 项目中组织的。
StateManagementController.java
目录
[1. 为什么需要 Cookie 和 Session?(背景知识)](#1. 为什么需要 Cookie 和 Session?(背景知识))
[2. 深入解析 Cookie(客户端的"就诊卡")](#2. 深入解析 Cookie(客户端的“就诊卡”))
[3. 深入解析 Session(服务器的"病历本")](#3. 深入解析 Session(服务器的“病历本”))
[4. Cookie 与 Session 的区别(面试/考试必考)](#4. Cookie 与 Session 的区别(面试/考试必考))
[5. 关于 Header(请求头)](#5. 关于 Header(请求头))
[1. 任何人都可以"伪造"和"篡改" (Cookie Forgery)](#1. 任何人都可以“伪造”和“篡改” (Cookie Forgery))
[2. 容易被"小偷"偷走 (XSS 攻击)](#2. 容易被“小偷”偷走 (XSS 攻击))
[3. 在路上容易被"偷听" (中间人攻击)](#3. 在路上容易被“偷听” (中间人攻击))
[4. 容易被"借刀杀人" (CSRF 攻击)](#4. 容易被“借刀杀人” (CSRF 攻击))
[1. 什么是 Cookie?(通俗理解)](#1. 什么是 Cookie?(通俗理解))
[2. HttpServletRequest 是什么?](#2. HttpServletRequest 是什么?)
[3. 两种获取方式的对比(核心考点)](#3. 两种获取方式的对比(核心考点))
[4. Java 增强型 For 循环](#4. Java 增强型 For 循环)
[扩展 1:@CookieValue 的隐形炸弹](#扩展 1:@CookieValue 的隐形炸弹)
[扩展 2:如何测试这段代码?(动手实践)](#扩展 2:如何测试这段代码?(动手实践))
[扩展 3:Cookie 的局限性与 Session](#扩展 3:Cookie 的局限性与 Session)
[扩展 4:Java 中的空指针异常(NullPointerException)](#扩展 4:Java 中的空指针异常(NullPointerException))
[1. 传统方式 (HttpServletRequest)](#1. 传统方式 (HttpServletRequest))
[2. 注解方式 (@SessionAttribute)](#2. 注解方式 (@SessionAttribute))
[3. Spring 内置对象方式 (HttpSession)](#3. Spring 内置对象方式 (HttpSession))
作为新手小白,我们不能只看代码,更要理解代码背后的"故事"。我将结合你提供的图片(Page 1 - Page 9)和 PDF 内容,为你构建一个完整的知识体系。
1. 为什么需要 Cookie 和 Session?(背景知识)
-
HTTP 的"健忘症":
资料 Page 1 中提到,HTTP 协议是**无状态(Stateless)**的。
-
小白解释:这就像你每天去同一家早餐店买豆浆,但老板不仅记不住你的名字,连你昨天付过钱办了会员卡都记不住。每一次见面,他都把你当成第一次来的陌生人。
-
问题:这在互联网上很麻烦。比如你刚在淘宝登录了,点击"购物车"页面,如果服务器记不住你,就会让你再登录一次。
-
-
解决方案:
我们需要一种机制来"维持关系"。资料 Page 2 用了极其生动的**"医院挂号"**例子:
-
就诊卡 (Cookie):医院给你一张卡,你每次去不同科室(访问不同页面)都出示这张卡,医生就知道你是谁。
-
病历本 (Session):医生电脑里存着你这张卡对应的病情记录(用户信息),这只有医生能看,卡上是不存病情的。
-
2. 深入解析 Cookie(客户端的"就诊卡")
资料来源:Page 6, Page 7
-
定义:Cookie 是服务器发送给浏览器的一小段文本信息,浏览器会把它存起来。下次访问同一个网站时,浏览器会自动把这段信息带过去。
-
代码中的知识点:
-
HttpServletRequest:这是 Java Web 中非常底层的对象。它代表了"请求"。你可以把它想象成一个快递包裹,里面装着浏览器发给服务器的所有东西(包括 Cookie)。 -
request.getCookies():这个方法不像拿苹果那么简单,它返回的是一个数组Cookie[]。为什么?因为一个网站可能给你发了好几张"卡"(比如一张存你的ID,一张存你上次浏览的商品ID)。 -
@CookieValue:这是 Spring 框架给我们的"糖"。传统的getCookies需要如果你想找特定的卡,得把所有卡倒出来一张张看名字(写 for 循环)。用了@CookieValue("bite"),相当于你跟助手说:"帮我把名字叫 bite 的那张卡找出来",助手直接就把卡给你,省去了循环的代码。
-
-
扩展知识点(重点):
-
安全性 :资料 Page 7 展示了你可以手动在浏览器里修改 Cookie (
bite:bite666)。这意味着 Cookie 是不安全的。- 举例 :如果你把"用户余额=1000"直接存在 Cookie 里,聪明的用户可能自己在浏览器里把它改成"用户余额=999999"。所以,重要信息(密码、余额)绝对不能直接存在 Cookie 里。
-
生命周期:Cookie 有存活时间。有的 Cookie 关掉浏览器就没了(会话级),有的可以存一个月(持久级,通过设置 Max-Age)。
-
3. 深入解析 Session(服务器的"病历本")
资料来源:Page 3, Page 4, Page 5, Page 8, Page 9
-
定义:Session 是服务器端的一种存储机制。它在服务器内存里开辟一块空间,专门用来存这个用户的信息。
-
原理(配合 Page 5 理解):
-
创建 :用户第一次来,服务器创建一个 Session 对象,并给它分配一个唯一的 SessionID (比如
123)。 -
关联 :服务器把这个
123放到 Cookie 里,发给浏览器。 -
识别 :下次浏览器带着 Cookie (
ID=123) 来,服务器拿着123去内存里找,就能找到对应的 Session 对象,从而知道由于是"张三"。
-
-
代码中的知识点:
-
request.getSession():这个方法有两个动作。如果浏览器带来的 ID 能找到 Session,就返回旧的;如果找不到,就自动创建一个新的。 -
session.setAttribute("key", value):Session 就像一个 Java 的Map(哈希表)。你可以往里面扔任何对象(String, User对象, 购物车List等)。 -
request.getSession(false):资料 Page 9 特别强调了false参数。这是为了性能和逻辑正确性。- 场景:查询用户是否登录。如果用户没登录(没 Session),你应该直接返回"未登录",而不是给他新建一个空的 Session。
-
-
扩展知识点(重点):
-
Session 丢失问题:资料 Page 5 提到"Session 默认保存在内存中,如果重启服务器则 Session 数据就会丢失"。
-
举例:双十一抢购,服务器突然重启了一下,所有正在排队的用户都会被踢出登录,因为内存里的 Session 全没了。
-
解决方案 :实际大厂开发中,会把 Session 存在 Redis(一种外部的高速缓存数据库)里,这样服务器重启也不怕。
-
-
分布式问题:如果你有两台服务器 A 和 B。用户第一次访问 A,Session 存在 A 的内存里。第二次负载均衡把请求分到了 B,B 的内存里没有这个 Session,用户就会莫名其妙被踢下线。这也是为什么要用 Redis 存 Session 的原因。
-
4. Cookie 与 Session 的区别(面试/考试必考)
根据资料 Page 5 的总结,我为你整理了一个对比表:
| 特性 | Cookie | Session | 比喻 |
|---|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端(内存) | 你的口袋 vs 医生的电脑 |
| 安全性 | 低(容易被篡改、伪造) | 高(数据在服务器,只有 ID 在外面跑) | 钞票 vs 银行存款记录 |
| 存储容量 | 小(通常限制 4KB) | 大(只受服务器内存限制) | 钱包大小 vs 仓库大小 |
| 数据类型 | 只能存字符串 | 可以存任意 Java 对象 | 纸条 vs 实物 |
| 依赖关系 | 通常不需要 Session | 通常依赖 Cookie 来传输 SessionID | - |
5. 关于 Header(请求头)
资料来源:PDF Page 12
-
User-Agent:这是最常用的 Header。它告诉服务器你是用电脑还是手机,用的是 Chrome 还是 Safari。
-
扩展应用:服务器可以根据 User-Agent 判断"如果是手机访问,就跳转到移动版页面;如果是电脑,就显示桌面版页面"。
给新手的学习建议
-
动手运行 :代码看百遍,不如手敲一遍。建议你把上面的
StateManagementController代码复制到你的 IDEA 里,启动 Spring Boot 项目。 -
抓包观察 :资料中提到了使用 Fiddler 或 浏览器开发者工具 (F12)。这是 Web 开发的神器。
-
按 F12 -> Network (网络)。
-
刷新页面。
-
点击请求,查看 Headers 里的
Request Headers(Cookie 在这) 和Response Headers(Set-Cookie 在这)。
-
-
理解流程:不要死记代码,要脑补"浏览器"和"服务器"一来一回的对话过程。
希望这份超过 4000 字量级的详细解析(含代码注释)能完美解决你的疑惑!如果还有哪里不懂,欢迎随时提问。
java
package com.example.demo.controller; // 1. 定义包名,就像是把文件放在哪个文件夹里
// 2. 导入需要用到的类(就像做菜前准备食材)
import org.springframework.web.bind.annotation.CookieValue; // 用于注解方式获取Cookie
import org.springframework.web.bind.annotation.RequestMapping; // 用于映射请求路径
import org.springframework.web.bind.annotation.RestController; // 标识这是一个控制器
import org.springframework.web.bind.annotation.SessionAttribute; // 用于注解方式获取Session属性
import javax.servlet.http.Cookie; // Java原生Servlet API中的Cookie类
import javax.servlet.http.HttpServletRequest; // 代表HTTP请求的对象
import javax.servlet.http.HttpServletResponse; // 代表HTTP响应的对象
import javax.servlet.http.HttpSession; // 代表会话Session的对象
/**
* Spring Web MVC 入门:Cookie 和 Session 详解控制器
* * 这个类演示了如何在 Spring MVC 中处理客户端和服务器之间的状态。
* 对应你提供的 PDF 和图片资料中的所有代码示例。
* * @RestController:
* 这是 @Controller 和 @ResponseBody 的组合注解。
* 意味着这个类中的所有方法返回的数据都会直接写入 HTTP 响应体中(比如显示在浏览器页面上),
* 而不是跳转到某个 HTML 模板文件。
* * @RequestMapping("/param"):
* 这是一个路由映射。表示这个类下面所有方法的访问路径都要加上 /param 前缀。
* 例如 method10 方法的完整访问路径就是 http://127.0.0.1:8080/param/m10
*/
@RestController
@RequestMapping("/param")
public class StateManagementController {
// ==========================================
// 第一部分:Cookie 机制 (对应资料 Page 6, 7)
// ==========================================
/**
* 1. 传统方式获取 Cookie (对应 Page 6)
* * 知识点:
* - HttpServletRequest: 是 Servlet 的标准接口,封装了客户端发来的所有请求信息。
* - HttpServletResponse: 封装了服务器发给客户端的所有响应信息。
* * 场景:想看看客户端(浏览器)带了哪些"就诊卡"(Cookie)来。
*/
@RequestMapping("/m10")
public String method10(HttpServletRequest request, HttpServletResponse response) {
// request.getCookies(): 从请求中拿到所有的 Cookie 对象。
// 注意:如果没有 Cookie,这个方法会返回 null,所以后面必须判空。
Cookie[] cookies = request.getCookies();
// StringBuilder: 用于高效地拼接字符串。在循环中拼接字符串如果不复用对象,会产生大量垃圾内存。
StringBuilder builder = new StringBuilder();
// 严谨的判空操作(小白容易忽略的点:直接循环可能导致空指针异常 NullPointerException)
if (cookies != null) {
// 增强型 for 循环 (foreach),遍历每一个 Cookie 对象
for (Cookie ck : cookies) {
// ck.getName(): 获取 Cookie 的名字(比如 "JSESSIONID" 或 "bite")
// ck.getValue(): 获取 Cookie 的值(比如 "bite666")
builder.append(ck.getName() + ":" + ck.getValue() + "; ");
}
}
// 将拼接好的结果返回给浏览器显示
return "Cookie信息: " + builder.toString();
}
/**
* 2. 简洁方式获取 Cookie (对应 Page 7)
* * 知识点:
* - @CookieValue: Spring 提供的注解,专门用来帮我们省去上面写 request.getCookies() 再循环查找的麻烦代码。
* * 场景:我明确知道我要找一个叫 "bite" 的 Cookie。
*/
@RequestMapping("/getCookie")
public String cookie(@CookieValue(value = "bite", required = false) String bite) {
// value = "bite": 指定要获取的 Cookie 名称。
// required = false: 这点很重要!
// 如果设为 true (默认),当浏览器没传这个 Cookie 时,程序会直接报错 400 Bad Request。
// 设为 false,如果没有这个 Cookie,变量 bite 就是 null,程序不会崩。
return "使用注解获取到的 bite 值: " + bite;
}
// ==========================================
// 第二部分:Session 机制 (对应资料 Page 8, 9, PDF Page 11-12)
// ==========================================
/**
* 3. 存储 Session (对应 Page 8)
* * 知识点:
* - Session (会话): 服务器端记录用户状态的机制(就像医院的"病历本")。
* - request.getSession(): 获取或创建 Session 的核心方法。
* * 场景:用户登录成功了,我要在服务器这边记下"这个用户是张三"。
*/
@RequestMapping("/setSess")
public String setsess(HttpServletRequest request) {
// request.getSession(true) (默认是 true):
// 逻辑:如果在服务器内存里能找到属于当前用户的 Session,就直接拿来用;
// 如果找不到(比如用户第一次来,或者太久没来过期了),就新建一个 Session。
HttpSession session = request.getSession();
if (session != null) {
// session.setAttribute("key", value): 往 Session 里存数据。
// 就像在 Map 里 put 数据一样。这里我们存了一个名为 "username",值为 "java" 的数据。
// 实际开发中,这里通常存 User 对象。
session.setAttribute("username", "java");
}
return "Session 存储成功!(服务器已经生成了 JSESSIONID 并通过 Cookie 发给浏览器了)";
}
/**
* 4. 读取 Session - 传统方式 (对应 Page 9)
* * 场景:用户访问了其他页面,我要看看他是不是之前登录过的"张三"。
*/
@RequestMapping("/getSess")
public String sess(HttpServletRequest request) {
// request.getSession(false):
// 关键区别:参数是 false!
// 逻辑:如果有 Session 就返回;如果没有,**千万别创建新的**,直接返回 null。
// 为什么?因为我们只是想读取旧数据,如果用户压根没登录,创建新 Session 浪费服务器内存。
HttpSession session = request.getSession(false);
String username = null;
// 必须要进行双重检查:
// 1. session != null: 确保用户有会话。
// 2. session.getAttribute("username") != null: 确保会话里确实存了 "username" 这个数据。
if (session != null && session.getAttribute("username") != null) {
// getAttribute 返回的是 Object 类型(所有类的父类),所以必须强制类型转换 (String) 成我们要的类型。
username = (String) session.getAttribute("username");
}
return "传统方式获取 username: " + username;
}
/**
* 5. 读取 Session - 注解方式 (对应 PDF Page 11)
* * 知识点:
* - @SessionAttribute: Spring 提供的注解,用于简化 Session 读取。
* * 场景:更优雅地获取 Session 数据。
*/
@RequestMapping("/getSess2")
public String sess2(@SessionAttribute(value = "username", required = false) String username) {
// 这里 Spring 会自动帮我们在 Session 中查找 key 为 "username" 的值,并注入给参数 username。
// 同样建议 required = false,防止 Session 过期导致报错。
return "注解方式获取 username: " + username;
}
/**
* 6. 读取 Session - Spring 内置对象方式 (对应 PDF Page 11-12,推荐)
* * 知识点:
* - 直接在参数里写 HttpSession session。
* - Spring MVC 非常智能,看到这个参数,就会自动把当前的 Session 对象注进来。
* - 这相当于 request.getSession(),如果不存在通常会自动创建。
*/
@RequestMapping("/getSess3")
public String sess3(HttpSession session) {
// 直接使用 session 对象获取数据
String username = (String) session.getAttribute("username");
return "内置对象方式获取 username: " + username;
}
// ==========================================
// 第三部分:Header 机制 (对应 PDF Page 12)
// ==========================================
/**
* 7. 获取请求头 Header (对应 PDF Page 12)
* * 场景:我想知道用户是用什么浏览器访问的(User-Agent)。
*/
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request) {
// getHeader("HeaderName"): 获取指定的请求头信息
String userAgent = request.getHeader("User-Agent");
return "用户的浏览器信息(User-Agent): " + userAgent;
}
}

cookie存储在电脑上为啥不安全?
这是一个非常棒的问题!在编程世界里,"不要相信客户端传来的任何数据" 是一条铁律。
简单来说,Cookie 之所以"不安全",是因为它存储在用户的手机或电脑(客户端)上 ,而不在服务器的控制范围内。
就像你把日记本放在公园的长椅上,别人不仅能偷看 ,还能偷偷改写 ,甚至直接拿走。
下面我用"通俗易懂的例子"配合"技术原理"为你详细拆解为什么它不安全,以及会有什么样的后果。
1. 任何人都可以"伪造"和"篡改" (Cookie Forgery)
-
生活比喻:
假设游乐园的门票是一张纸质卡片,上面手写着"VIP用户"。这张卡片就在你手里(客户端)。
如果你是个"坏孩子",你自己拿支笔,把"普通用户"改成"VIP用户",或者把"过期时间:昨天"改成"明天"。如果检票员(服务器)只看卡片不查系统,你就混进去了。
-
技术原理:
Cookie 是纯文本文件。任何用户都可以按下 F12 打开浏览器的开发者工具(Application 标签页),找到 Cookie,然后双击里面的值,想改什么改什么。
- 危险场景: 如果你把
isLogin=false改成了isLogin=true,或者把userId=100(普通人)改成了userId=1(管理员),而后端没有做二次校验,服务器就会误以为你是管理员。
- 危险场景: 如果你把
2. 容易被"小偷"偷走 (XSS 攻击)
-
生活比喻:
这就像你的"门禁卡"挂在脖子上。如果你去了一个坏人开的店(恶意网站),或者有人在你家门口贴了一张带胶水的纸(恶意脚本),当你经过时,你的门禁卡就被"粘"走或者被复制了。
-
技术原理(跨站脚本攻击 XSS):
黑客可以在网页的评论区、搜索框里植入一段恶意的 JavaScript 代码(比如 <script>alert(document.cookie)</script>)。
当其他用户打开这个网页时,这段代码会自动运行,把该用户的 Cookie(里面可能包含 SessionID)发送给黑客。
- 后果: 黑客拿到了你的
JSESSIONID,以后他不需要账号密码,直接拿着这个 ID 就能登录你的淘宝、银行账户(只要你的会话还没过期)。
- 后果: 黑客拿到了你的
3. 在路上容易被"偷听" (中间人攻击)
-
生活比喻:
你和朋友打电话报银行卡密码。如果你们在一个嘈杂的咖啡馆,旁边坐着一个戴耳机的"间谍",他虽然不说话,但他听到了你报的所有数字。
-
技术原理(HTTP 明文传输):
Cookie 是随着每一次 HTTP 请求发送的。如果你的网站不是 HTTPS(加密传输),而是普通的 HTTP,那么你在免费 Wi-Fi 下上网时,黑客可以用抓包工具(如 Wireshark)截获你在网络上传输的所有数据包,直接看到你的 Cookie 内容。
4. 容易被"借刀杀人" (CSRF 攻击)
-
生活比喻:
你已经登录了银行网站(Cookie 还在有效期内)。这时你点开了一个中奖广告(恶意链接)。这个广告背地里偷偷向银行发了一条指令:"给黑客转账 1000 元"。
因为你的浏览器里存着银行的 Cookie,银行一看:"嗯,这是本人发的请求(带有正确的 Cookie)",于是钱就转走了。
-
技术原理(跨站请求伪造 CSRF):
利用浏览器"自动发送 Cookie"的机制,在用户不知情的情况下,借用用户的身份(Cookie)向服务器发送恶意请求。
我们该怎么防御?(给新手的安全锦囊)
虽然 Cookie 本身不安全,但我们有很多手段给它穿上"防弹衣":
-
HttpOnly(关键):
-
在后端设置 Cookie 时,加上
HttpOnly属性。 -
作用: 告诉浏览器,"这个 Cookie 只能通过 HTTP 请求发送,禁止 JavaScript 代码(
document.cookie)读取它"。这样 XSS 攻击就偷不走 Cookie 了。
-
-
HTTPS(关键):
-
全站使用 HTTPS 加密。
-
作用: 给传输通道加了防盗门。即使黑客截获了数据包,看到的也是乱码,根本不知道 Cookie 是什么。
-
-
Secure 属性:
-
设置 Cookie 的
Secure属性。 -
作用: 规定这个 Cookie 只能在 HTTPS 连接下发送,HTTP 连接时不发送,防止意外泄露。
-
-
不要在 Cookie 里存敏感数据:
-
绝对不要在 Cookie 里直接存"密码"、"余额"、"手机号"。
-
正确做法: 只存一个毫无意义的随机字符串(SessionID)。真正的用户数据存在服务器的 Session 里(保险柜里)。即使黑客拿到了 SessionID,也看不到你的密码。
-
总结
Cookie 就像你手里的钥匙。
-
如果你把钥匙扔在大街上(HTTP 明文),谁都能捡。
-
如果你把钥匙贴上名字和地址(存敏感信息),捡到的人就能去你家。
-
如果你不给钥匙加防盗链(不设 HttpOnly),小偷能轻易摸走。
所以,Cookie 本身只是个载体,安不安全取决于你怎么用它。

你好!非常欢迎来到 Java Web 开发的世界。看到你提供的代码截图,我能感觉到你正在学习 Spring MVC 中关于 Cookie(客户端会话技术) 的处理方式。
这对于新手来说是一个非常重要的里程碑,因为理解 Cookie 是理解"网站如何记住用户"(比如保持登录状态)的关键。
别担心,我会把你当成完全的"小白",用最通俗易懂的语言,配合生活中的例子,为你详细拆解这段代码,并补充完整的上下文和扩展知识。这篇回答会比较长,建议你在这个页面收藏起来慢慢看。
第一部分:完整代码复原与超详细注释
截图中的代码只是两个方法片段,无法直接运行。为了让你能看懂它的"全貌",我为你补全了类定义、导包(Import)等必要部分。
阅读指南: 请仔细阅读代码中的绿色注释,我把每一行代码背后的逻辑都写进去了。
Java
java
/**
* 这是一个典型的 Spring MVC 控制器类(Controller)。
* 它的作用是:接收浏览器发来的请求,处理业务逻辑,然后返回结果给浏览器。
*/
// 1. 导入处理 Cookie 的核心类,这是 Java Servlet 标准库提供的
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
// 2. 导入 Spring MVC 的核心注解,用于将网址映射到 Java 方法上
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @RestController 是 Spring 的注解。
* 意思告诉程序:"这是一个控制器类,而且这个类里所有方法的返回值,
* 都会直接作为字符串(或JSON)写入到浏览器页面上,而不是跳转到某个 HTML 文件。"
*/
@RestController
public class CookieDemoController {
// ==========================================
// 方法一:传统方式(原生 Servlet API)获取 Cookie
// ==========================================
/**
* @RequestMapping("/r13")
* 这是一个路由映射。
* 意思:当你在浏览器地址栏输入 "http://localhost:8080/r13" 时,
* Spring 就会自动运行下面这个 r13 方法。
*/
@RequestMapping("/r13")
public String r13(HttpServletRequest request) {
// HttpServletRequest 是一个非常重要的对象,它代表了"浏览器发给服务器的那个请求包"。
// 这个包里装了所有东西:浏览器版本、IP地址、参数,当然也包括 Cookie。
// 1. 获取所有的 Cookie
// request.getCookies() 会去翻请求头里的数据,把所有的 Cookie 拿出来。
// 因为 Cookie 可能有很多个(比如 JSESSIONID, username, language 等),所以它返回的是一个数组(Cookie[])。
Cookie[] cookies = request.getCookies();
// 2. 判空检查(这是新手必须养成的好习惯!)
// 如果浏览器从未访问过这个网站,或者手动清空了缓存,request.getCookies() 可能会返回 null。
// 如果不检查直接用,程序就会报"空指针异常(NullPointerException)"并在后台崩溃。
if (cookies != null) {
// 3. 遍历数组(增强型 for 循环)
// 意思:依次从 cookies 数组里拿出一个个 cookie,赋值给变量 c,直到拿完为止。
for (Cookie cookie : cookies) {
// 4. 打印输出
// getName() 获取 Cookie 的名字(比如 "username")
// getValue() 获取 Cookie 的值(比如 "zhangsan")
// System.out.println 会把结果打印在 IDEA 的控制台里(不是浏览器页面上)。
System.out.println(cookie.getName() + ":" + cookie.getValue());
}
}
// 5. 返回结果给浏览器
// 浏览器页面上会显示这句话。
return "返回Cookie成功";
}
// ==========================================
// 方法二:Spring 注解方式(更高级、更简单)获取 Cookie
// ==========================================
/**
* @RequestMapping("/r14")
* 映射地址:访问 "http://localhost:8080/r14" 时触发此方法。
*/
@RequestMapping("/r14")
// 下面这行代码是重点中的重点:
// @CookieValue("java"):告诉 Spring,"请帮我去所有的 Cookie 里找一个名字叫 'java' 的 Cookie"。
// String java:如果不为空,就把那个 Cookie 的值,自动赋值给这个 String 类型的变量 java。
public String r14(@CookieValue("java") String java) {
// 这里的逻辑就非常简单了,因为 Spring 帮你省去了上面方法 r13 里那一堆"获取数组、判空、循环查找"的代码。
// 直接拿到了值,我们就拼接一个字符串返回。
// 注意:如果浏览器里没有叫 "java" 的 cookie,默认情况下这个方法会报错(400 Bad Request)。
// 扩展知识里我会讲怎么解决这个问题。
return "从Cookie中获取Java的值: " + java;
}
}
第二部分:核心知识点深度解析(新手必读)
这段代码虽然短,但它展示了 Java Web 开发演变的两个阶段:原生时代 vs 框架时代。
1. 什么是 Cookie?(通俗理解)
-
概念:HTTP 协议是"健忘"的(无状态)。你访问了页面 A,再访问页面 B,服务器是不知道这是同一个人的。
-
例子:这就像你去办了一张健身房会员卡。
-
服务器(健身房前台) 第一次见到你,给你发了一张卡片(Set-Cookie),上面写着你的会员号。
-
你(浏览器) 把卡片塞进钱包(保存 Cookie)。
-
以后你每次去健身房,不需要说话,直接出示卡片(Request Header 带上 Cookie)。
-
前台一看卡片:"哦,是王先生啊,请进。"
-
-
代码对应 :
request.getCookies()就是前台工作人员在看你手里有没有卡片。
2. HttpServletRequest 是什么?
-
在 Java Web 中,只要浏览器发请求过来,Tomcat(服务器软件)就会把这次请求的所有信息打包成一个对象,就是
HttpServletRequest。 -
它包含:
-
你从哪来?(IP地址)
-
你想干啥?(URL 路径,如
/r13) -
你带了啥?(参数、Cookie、Header)
-
-
在方法
r13中,我们通过在这个大包裹里翻找,才找到了 Cookie。
3. 两种获取方式的对比(核心考点)
| 特性 | 方法 r13 (原生 Servlet API) | 方法 r14 (Spring 注解 @CookieValue) |
|---|---|---|
| 代码量 | 繁琐(获取、判空、循环) | 极简(一行参数搞定) |
| 灵活性 | 高。可以拿到所有 Cookie,也可以处理不存在的情况。 | 专注。通常只用来拿某一个特定的 Cookie。 |
| 底层原理 | 手动操作底层数据结构。 | 依赖注入。Spring 框架在后台帮你把 r13 的活儿干完了,直接把结果给你。 |
| 新手推荐 | 先学这个,理解原理。 | 理解原理后,工作中用这个,效率高。 |
4. Java 增强型 For 循环
在 r13 中出现的 for (Cookie cookie : cookies) 是 Java 5 引入的语法糖。
-
翻译成人话 : "对于
cookies盒子里的每一个元素,把它拿出来叫cookie,然后执行一遍大括号里的代码。" -
这比传统的
for(int i=0; i<cookies.length; i++)写法要不容易出错(不会导致数组越界)。
第三部分:扩展知识点与避坑指南(进阶必读)
作为新手,只看懂代码是不够的,你需要知道在实际工作中会遇到什么问题。
扩展 1:@CookieValue 的隐形炸弹
在方法 r14 中:
Java
java
public String r14(@CookieValue("java") String java)
如果浏览器中没有 一个名字叫 "java" 的 Cookie,你访问 /r14 时,页面会直接报错:
HTTP Status 400 - Bad Request
Message: Missing cookie 'java' for method parameter of type String
如何解决?
如果你希望"有就获取,没有也别报错",你需要这样写:
Java
java
// required = false 表示这个 Cookie 不是必须的
// defaultValue 表示如果没读到,就给它一个默认值 "unknown"
public String r14(@CookieValue(value = "java", required = false, defaultValue = "unknown") String java) {
return "值是: " + java;
}
重点:这是开发中最常用的写法,能极大提高程序的健壮性。
扩展 2:如何测试这段代码?(动手实践)
很多新手写完代码不知道怎么测,因为 Cookie 通常是服务器生成的。但我们可以用浏览器"伪造"一个来测试获取功能。
-
启动你的 Spring Boot 项目。
-
打开 Chrome 浏览器,访问
http://localhost:8080/r13。 -
按
F12打开开发者工具,点击顶部的 Application(应用)标签。 -
在左侧栏找到 Storage -> Cookies -> 点击你的网址。
-
在右侧空白表格里,双击 Name 输入
java,双击 Value 输入HelloSpring。 -
刷新页面
http://localhost:8080/r14。 -
你会发现页面上显示了:"从Cookie中获取Java的值: HelloSpring"。
扩展 3:Cookie 的局限性与 Session
虽然 Cookie 很好用,但它有两个大问题:
-
不安全:Cookie 存在用户电脑里,用户可以随意修改(就像上面的测试一样)。你不能把"是否已登录:是"这种关键信息直接明文放在 Cookie 里。
-
大小限制:一个 Cookie 通常只能存 4KB 的数据。
扩展概念 - Session(会话):
为了解决安全问题,Java 引入了 Session。
-
原理 :重要数据(如用户余额、登录状态)存在服务器端(Session)。
-
关联 :服务器只给浏览器发一个没有意义的随机乱码 ID(通常叫
JSESSIONID),放在 Cookie 里。 -
流程 :浏览器带着
JSESSIONID的 Cookie 来 -> 服务器拿着 ID 去 Session 库里查 -> 找到了对应的数据。 -
总结:Cookie 是 Session 的"钥匙"。
扩展 4:Java 中的空指针异常(NullPointerException)
在 r13 代码中:
Java
java
if (cookies != null) { ... }
这行代码价值千金。
在 Java 中,如果你试图去"操作"一个空(null)对象(比如调用它的方法),程序会立刻崩溃。
-
新手陷阱 :很多新手会直接写
for (Cookie cookie : request.getCookies())。 -
后果 :如果
getCookies()返回了null,增强型 for 循环底层会尝试去访问它,导致 Crash。一定要先判空!
第四部分:总结与建议
对代码的总结:
这张图片展示了 Spring MVC 中获取 Cookie 的两种流派:
-
r13(学院派) :通过HttpServletRequest全量获取,手动遍历。适合复杂逻辑或调试。 -
r14(实战派) :通过@CookieValue精准获取。代码优雅,适合绝大多数业务场景。
给新手小白的建议(Next Steps):
-
不要只看,要敲代码:把上面的代码手打一遍到你的 IDE(IntelliJ IDEA)里。
-
尝试"设置"Cookie:你现在学会了"获取"(Read),下一步建议去搜索"Java HttpServletResponse addCookie",学习如何由服务器向浏览器"发送/写入"(Write)一个 Cookie。
-
理解 HTTP 协议:Java Web 的本质就是处理 HTTP 协议。了解 Request(请求头、请求体)和 Response(响应头、响应体)的结构,会让你学 Java 后端事半功倍。
希望这份详尽的解释能帮你彻底搞懂这两个方法!如果有任何报错或者哪里卡住了,随时把错误信息发给我。加油!
这三种读取 Session 的方法代表了 Java Web 开发从"原生 Servlet"到"Spring 高级封装"的演变过程。
以下是这三种方式的详细对比、优缺点分析及使用建议。
核心对比总结表
| 特性 | 方法 1: HttpServletRequest | 方法 2: @SessionAttribute | 方法 3: HttpSession (参数) |
|---|---|---|---|
| 层级 | 底层 (原生 Servlet API) | 高层 (Spring 注解封装) | 中层 (Spring 自动注入) |
| 代码量 | 最多 (繁琐) | 最少 (优雅) | 中等 |
| Session 创建控制 | 最强 (可精确控制不创建新会话) | 弱 (主要用于读取) | 较弱 (通常不存在时会自动创建) |
| 类型转换 | 需要手动强转 (String) |
自动注入 (无需显式强转) | 需要手动强转 (String) |
| 空值处理 | 需要显式编写 if != null |
通过 required = false 控制 |
需要显式编写 if != null |
| 适用场景 | 极度关注性能、需要底层控制时 | 只需读取某个特定属性时 | 需要读写 Session 或操作 Session (如销毁) |
详细深度解析
java
/**
* 4. 读取 Session - 传统方式 (对应 Page 9)
* * 场景:用户访问了其他页面,我要看看他是不是之前登录过的"张三"。
*/
@RequestMapping("/getSess")
public String sess(HttpServletRequest request) {
// request.getSession(false):
// 关键区别:参数是 false!
// 逻辑:如果有 Session 就返回;如果没有,**千万别创建新的**,直接返回 null。
// 为什么?因为我们只是想读取旧数据,如果用户压根没登录,创建新 Session 浪费服务器内存。
HttpSession session = request.getSession(false);
String username = null;
// 必须要进行双重检查:
// 1. session != null: 确保用户有会话。
// 2. session.getAttribute("username") != null: 确保会话里确实存了 "username" 这个数据。
if (session != null && session.getAttribute("username") != null) {
// getAttribute 返回的是 Object 类型(所有类的父类),所以必须强制类型转换 (String) 成我们要的类型。
username = (String) session.getAttribute("username");
}
return "传统方式获取 username: " + username;
}
/**
* 5. 读取 Session - 注解方式 (对应 PDF Page 11)
* * 知识点:
* - @SessionAttribute: Spring 提供的注解,用于简化 Session 读取。
* * 场景:更优雅地获取 Session 数据。
*/
@RequestMapping("/getSess2")
public String sess2(@SessionAttribute(value = "username", required = false) String username) {
// 这里 Spring 会自动帮我们在 Session 中查找 key 为 "username" 的值,并注入给参数 username。
// 同样建议 required = false,防止 Session 过期导致报错。
return "注解方式获取 username: " + username;
}
/**
* 6. 读取 Session - Spring 内置对象方式 (对应 PDF Page 11-12,推荐)
* * 知识点:
* - 直接在参数里写 HttpSession session。
* - Spring MVC 非常智能,看到这个参数,就会自动把当前的 Session 对象注进来。
* - 这相当于 request.getSession(),如果不存在通常会自动创建。
*/
@RequestMapping("/getSess3")
public String sess3(HttpSession session) {
// 直接使用 session 对象获取数据
String username = (String) session.getAttribute("username");
return "内置对象方式获取 username: " + username;
}
1. 传统方式 (HttpServletRequest)
这是最"原教旨主义"的写法,直接操作 Servlet API。
-
核心逻辑 :
request.getSession(false)。 -
优点:
-
内存控制最精细 :通过传入
false,你可以明确告诉服务器:"如果用户没登录(没 Session),千万别给我创建一个新的。"这对防止恶意爬虫制造大量垃圾 Session(导致内存溢出)非常有用。 -
全能 :拿到了
request对象,除了操作 Session,还能顺便获取 IP 地址、User-Agent 等信息。
-
-
缺点:
-
啰嗦 :你需要写多层
if判断(判断 Session 是否为空,判断 Attribute 是否为空)。 -
不优雅:业务代码中充斥着非业务逻辑的底层 API 调用。
-
2. 注解方式 (@SessionAttribute)
这是 Spring MVC 特有的"声明式"写法,由 Spring 框架在后台帮你完成查找和注入。
-
核心逻辑 :
@SessionAttribute(value = "username", required = false)。 -
优点:
-
最简洁:一行代码搞定,直接把 Session 中的值变成了方法参数。
-
语义清晰 :一看参数就知道这个方法依赖 Session 中的
username。 -
解耦:你的方法不再依赖 Servlet API(方便进行单元测试)。
-
-
缺点:
-
功能受限 :它只能用来读取 数据。如果你想
setAttribute(存数据)或者invalidate(注销/销毁 Session),这个注解做不到,你还是得把HttpSession对象拿出来。 -
异常风险 :如果忘记写
required = false且 Session 中没有该数据,Spring 会直接抛出400 Bad Request异常,导致页面报错。
-
3. Spring 内置对象方式 (HttpSession)
这是目前最常用的折中方案。Spring 检测到方法参数中有 HttpSession,会自动将当前的会话对象注入进来。
-
核心逻辑 :直接使用参数
session。 -
优点:
-
方便且强大 :既比
request方式少写代码,又拥有HttpSession的全部控制权(读、写、销毁)。 -
符合直觉:大多数业务场景下,我们假设用户访问需要 Session 的页面时,Session 应该是存在的(或者框架自动创建)。
-
-
缺点:
-
隐式创建 :通常情况下,Spring 注入这个对象等同于调用
getSession(true)。如果你的系统对"很多未登录用户访问此接口"极其敏感(担心创建过多空 Session 占用内存),这种方式不如方法 1 精确。 -
类型转换 :依然需要手动
(String)强转。
-
场景化建议:我该用哪个?
-
场景 A:你需要判断用户是否登录,且不想浪费内存(比如首页、公共页)
-
👉 首选 方法 1 (
HttpServletRequest)。 -
理由 :利用
getSession(false)避免为游客创建无用的 Session 对象。
-
-
场景 B:用户已经登录,你需要在一个详情页仅仅读取一下用户的名字或 ID
-
👉 首选 方法 2 (
@SessionAttribute)。 -
理由:代码最干净,不需要关心 Session 对象本身,只关心数据。
-
-
场景 C:用户登录、注销,或者购物车操作(既要读又要写)
-
👉 首选 方法 3 (
HttpSession)。 -
理由 :你需要完整的 Session 对象来执行
session.setAttribute()或session.invalidate(),这种方式比request方式更简单。
-
下一步建议:
既然你已经掌握了 Session 的读取,是否需要我为你展示如何优雅地通过拦截器(Interceptor)来统一处理 Session 登录检查? 这样就不需要在每个 Controller 方法里重复写判断逻辑了。