**摘要:**本文系统介绍了 HTTP 无状态协议下的状态管理方案,重点讲解了 Cookie 和 Session 的工作原理、区别及在 Spring MVC 中的实际应用。文章首先通过医院就诊卡类比解释了令牌机制,然后详细阐述了 Session 的概念、作用、本质和工作流程。接着对比了 Cookie 与 Session 在存储位置、安全性、容量等方面的差异,并提供了获取 Cookie 和 Session 的多种方式(包括 HttpServletRequest、注解等)。最后通过运行示例演示了 Session 的设置与获取过程,并介绍了获取 HTTP Header 的方法以及 @RestController 与 @Controller 的区别与适用场景。
HTTP 协议本身是"无状态"协议,这意味着默认情况下,客户端与服务器之间的每次通信都是独立的,没有直接的关联关系。
"无状态"的含义:
默认情况下,HTTP 协议的客户端和服务器之间的本次通信与下次通信之间没有直接联系。每次请求都是独立的,服务器不会记住之前的请求信息。
但在实际开发中,我们经常需要知道请求之间的关联关系。例如,用户登录网站成功后,第二次访问时服务器需要能够识别该用户是否已经登录。

上述图中的"令牌"通常存储在 Cookie 字段中,用于在客户端保存用户状态信息。
医院就诊卡类比:
- 看病前先挂号,提供身份证后获得"就诊卡",相当于患者的"令牌"
- 后续检查、诊断、开药等操作,只需出示就诊卡即可识别身份
- 看完病后可以注销就诊卡,销毁身份与卡的关联(类似网站注销)
- 再次看病时,可以办理新的就诊卡,获得新的"令牌"
- 建档 → 医院返回就诊卡
- 看病 → 携带就诊卡
就诊卡 ≈ 令牌 ≈ Cookie
服务器端需要记录"令牌"信息及其对应的用户信息,这就是 Session 机制的作用。
理解 Session
什么是会话?
会话(Session)在计算机领域指客户端与服务器之间持续不断的请求-响应交互过程。服务器能够识别来自同一客户端的连续请求,从而维持状态信息。

客服电话类比:
每次拨打客服电话是一个会话,挂断电话会话结束。再次拨打是新的会话。如果长时间不说话(无新请求),会话也会自动结束。
Session 的作用
服务器同时处理大量请求,需要区分每个请求属于哪个用户(哪个会话)。Session 就是服务器为保存用户信息而创建的特殊对象。

Session 的本质
Session 本质上是一个"哈希表"结构,存储键值对数据。Key 是 SessionID,Value 是用户信息。

SessionId 与 Token:
SessionId 是服务器生成的唯一字符串,从 Session 机制角度看称为"SessionId",从登录流程角度看也可称为"Token"。令牌除了 ID 外,通常还包含时间、签名等信息。
Session 工作流程

- **登录阶段:**用户登录时,服务器在 Session 中新增记录,并通过 HTTP 响应的 Set-Cookie 字段将 sessionId 返回给客户端
- **请求阶段:**客户端后续请求时,通过 HTTP 请求的 Cookie 字段携带 sessionId
- **验证阶段:**服务器根据 sessionId 在 Session 信息中查找对应用户信息,找不到则重新创建 Session 并返回新的 sessionId

**注意:**Session 默认保存在服务器内存中,重启服务器会导致 Session 数据丢失。生产环境通常需要持久化存储方案。
Cookie 与 Session 的区别
| 对比项 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端 |
| 安全性 | 较低(客户端可查看修改) | 较高(服务器端存储) |
| 存储容量 | 较小(通常 4KB) | 较大(受服务器内存限制) |
| 生命周期 | 可设置过期时间 | 会话结束或超时销毁 |
| 关联关系 | 通过 SessionId 关联,SessionId 通常存储在 Cookie 中 |
补充说明:
- Cookie 不仅可以存储 SessionId,还可以存储其他客户端数据
- SessionId 不一定通过 Cookie 传递,也可以通过 URL 参数等方式传递
- 两者经常配合使用,但并非必须同时使用
获取 Cookie
方式一:通过 HttpServletRequest
java
@RequestMapping("/getcookie")
public Boolean getCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return false;
}
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + ":" + cookie.getValue());
}
return true;
}
方式二:遍历 Cookie 数组
java
@RequestMapping("/getcookie2")
public String getCookie2(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
System.out.println(cookie.getName() + ":" + cookie.getValue());
}
}
return "获取 cookie 成功";
}
方式三:使用 @CookieValue 注解
java
@RequestMapping("/getcookie3")
public String getCookie3(@CookieValue("class") String className) {
return "获取 cookie 成功 className:" + className;
}
**技术背景:**Spring MVC 基于 Servlet API 构建。HttpServletRequest 和 HttpServletResponse 是 Servlet 提供的核心类,分别代表客户端请求和服务器响应。Spring MVC 对这些对象进行了封装,提供了更简洁的使用方式。
获取 Session
Session 存储
方式一:通过 HttpServletRequest 获取
java
@RequestMapping("/setsession")
public String setSession(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("name", "zhangsan");
session.setAttribute("age", 18);
session.setAttribute("classId", 20);
return "session 设置成功";
}
方式二:直接注入 HttpSession
java
@RequestMapping("/setsession2")
public String setSession2(HttpSession session) {
session.setAttribute("name", "zhangsan");
session.setAttribute("age", 18);
session.setAttribute("classId", 20);
return "session 设置成功";
}
方式三:安全判断版本
java
@RequestMapping("/setsession3")
public String setSession3(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session != null) {
session.setAttribute("name", "zhangsan");
session.setAttribute("age", 18);
session.setAttribute("classId", 20);
}
return "session 设置成功";
}
**原理说明:**getSession() 方法内部会自动从请求的 Cookie 中提取 SessionId,然后根据 SessionId 获取对应的 HttpSession 对象。开发者无需直接操作 SessionId。

Session 读取
方式一:通过 HttpServletRequest 读取
java
@RequestMapping("/getsession")
public String getSession(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session != null) {
String name = (String) session.getAttribute("name");
Integer age = (Integer) session.getAttribute("age");
Integer classId = (Integer) session.getAttribute("classId");
return "name: " + name + " age: " + age + " classId: " + classId;
}
return "";
}
方式二:直接注入 HttpSession 读取
java
@RequestMapping("/getsession2")
public String getSession2(HttpSession session) {
if (session != null) {
String name = (String) session.getAttribute("name");
Integer age = (Integer) session.getAttribute("age");
Integer classId = (Integer) session.getAttribute("classId");
return "name: " + name + " age: " + age + " classId: " + classId;
}
return "";
}
方式三:使用 @SessionAttribute 注解
java
@RequestMapping("/getsession3")
public String getSession3(@SessionAttribute("name") String name) {
return "name:" + name;
}
**注意:**方式三要求 session 不能为空,且指定的属性必须存在。
运行示例
1. 设置 Session

2. 观察 HTTP 通信
通过 Fiddler 观察 HTTP 请求和响应:


可以看到,HTTP 响应中通过 Set-Cookie 字段告知客户端将 SessionID 存储在 Cookie 中。
3. 浏览器查看结果

4. 获取 Session

5. 验证 SessionId 传递


可以看到,HTTP 请求时将 SessionId 通过 Cookie 传递到服务器。
获取 Header
方式一:通过 HttpServletRequest
java
@RequestMapping("/getheader")
public String getHeader(HttpServletRequest request) {
String header = request.getHeader("User-Agent");
System.out.println(header);
return "User-Agent:" + header;
}


方式二:使用 @RequestHeader 注解
java
@RequestMapping("/getheader2")
public String getHeader2(@RequestHeader("User-Agent") String name) {
return "User-Agent:" + name;
}
响应处理
返回静态页面
java
@Controller
public class ReturnController {
// 返回静态页面
@RequestMapping("/index")
public Object index() {
return "/hello.html";
}
}


@RestController 与 @Controller 对比
关联关系
- 继承关系:@RestController 是 @Controller 的特殊形式,结合了 @Controller 和 @ResponseBody 的功能
- **共同点:**两者都是 Spring MVC 中的控制器注解,用于处理 HTTP 请求
- **使用场景:**都可以用于构建 Web 应用程序
主要区别
| 对比项 | @Controller | @RestController |
|---|---|---|
| 返回值处理 | 返回视图名称(String),由视图解析器解析为 HTML 页面 | 返回对象或集合,自动序列化为 JSON/XML 等格式 |
| 注解组合 | 单独使用 @Controller | @Controller + @ResponseBody 的组合注解 |
| 适用场景 | 传统 MVC 应用,需要返回 HTML 页面 | RESTful API,前后端分离项目 |
| 响应类型 | 通常返回 ModelAndView 或视图名称 | 直接返回数据对象,HTTP 响应体为 JSON/XML |
| 是否需要 @ResponseBody | 需要显式添加 @ResponseBody 才能返回 JSON | 默认所有方法都相当于添加了 @ResponseBody |
| 示例代码 | java @Controller public class UserController { @RequestMapping("/user") public String getUser(Model model) { model.addAttribute("name", "张三"); return "user"; // 返回视图名称 } } |
java @RestController public class UserController { @RequestMapping("/user") public User getUser() { User user = new User("张三", 25); return user; // 自动转为 JSON } } |
使用建议:
- **传统 Web 应用:**使用 @Controller,配合视图模板(如 Thymeleaf、JSP)
- **前后端分离项目:**使用 @RestController,提供 RESTful API 接口
- **混合项目:**可以同时使用两者,@Controller 处理页面请求,@RestController 处理 API 请求
总结:@RestController 是 Spring 4.0 引入的便捷注解,专门用于构建 RESTful Web 服务。它简化了 API 开发,避免了在每个方法上重复添加 @ResponseBody 注解。

