发布于2026-08-05 阅读(0)
扫一扫,手机访问
要理解Cookie和Session,得先搞清楚一个根本问题:HTTP协议本身是“无状态”的。
所谓无状态,说白了就是协议本身不会记录任何历史请求信息——服务器处理完一次请求后,就把这次请求是谁发的给“忘”了。下次同一客户端再发请求,服务器依然不认识你。
打个比方:你打客服电话,每次接通都是不同的接线员,你必须把问题从头到尾重新说一遍。
但实际业务中,“记住用户”是刚需——登录状态要保持、购物车不能刷新就清空、表单要跨页面填写。正是这种业务有状态与协议无状态之间的矛盾,催生了Cookie + Session 机制。
理解 Cookie 与 Session 最好的方式,是类比就诊流程:
| 概念 | 类比 | 存储位置 | 存放内容 | 安全性 |
|---|---|---|---|---|
| Cookie | 就诊卡 / 学生证 | 客户端(浏览器) | 仅存放唯一标识(SessionID) | 低,本地可伪造 |
| Session | 医院档案 / 学校学籍 | 服务端(服务器内存) | 完整用户隐私数据 | 高,无法伪造 |
完整就诊流程:
对应到 Web 开发:
SessionID;Set-Cookie 响应头将 SessionID 存入浏览器 Cookie;SessionID,匹配对应用户的 Session;
实战代码:在 Spring Boot 中操作 Cookie 和 Session
以下代码完整演示了从 Cookie 读取、Session 存取的三种方式:
package com.spring.demo;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/request2")
public class RequestController2 {
// ========== 一、读取 Cookie ==========
/**
* 方式一:通过 HttpServletRequest 手动遍历 Cookie 数组
* 适用场景:需要批量处理所有 Cookie 时
*/
@RequestMapping("/getCookie")
public String getCookie(HttpServletRequest request) {
// 通过 request 对象获取所有 Cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
// 打印每个 Cookie 的 key 和 value
System.out.println(cookie.getName() + " : " + cookie.getValue());
}
}
return "cookie读取成功";
}
/**
* 方式二:通过 @CookieValue 注解直接获取指定 Cookie 的值
* 适用场景:只需要某个特定 Cookie 时,代码更简洁
*/
@RequestMapping("/getCookie2")
public String getCookie2(@CookieValue("class") String className) {
return "cookie读取成功, className:" + className;
}
// ========== 二、操作 Session ==========
/**
* 设置 Session 属性
* request.getSession() 无参调用:有则返回,无则新建
*/
@RequestMapping("/setSession")
public String setSession(HttpServletRequest request) {
HttpSession session = request.getSession(); // 获取或创建 Session
session.setAttribute("name", "zmt"); // 向 Session 中存入数据
return "session设置成功";
}
/**
* 读取 Session — 方式一:通过 request.getSession()
*/
@RequestMapping("/getSession")
public String getSession(HttpServletRequest request) {
HttpSession session = request.getSession();
// 从 Session 中取出之前存入的数据
String name = (String) session.getAttribute("name");
return "session读取成功, name:" + name;
}
/**
* 读取 Session — 方式二:直接注入 HttpSession 参数
* Spring 会自动将当前请求对应的 Session 注入进来
*/
@RequestMapping("/getSession2")
public String getSession2(HttpSession session) {
String name = (String) session.getAttribute("name");
return "session读取成功, name:" + name;
}
/**
* 读取 Session — 方式三:通过 @SessionAttribute 注解
* 最简洁的方式,直接获取 Session 中某个属性的值
*/
@RequestMapping("/getSession3")
public String getSession3(@SessionAttribute("name") String name) {
return "session读取成功, name:" + name;
}
// ========== 三、读取请求头 ==========
/**
* 方式一:通过 request.getHeader() 获取
*/
@RequestMapping("/getHeader")
public String getHeader(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return "header读取成功, userAgent:" + userAgent;
}
/**
* 方式二:通过 @RequestHeader 注解直接获取
*/
@RequestMapping("/getHeader2")
public String getHeader2(@RequestHeader("User-Agent") String userAgent) {
return "header读取成功, userAgent:" + userAgent;
}
}
代码思路解析:
request.getCookies() 返回数组需要手动遍历,不够优雅;Spring 提供了 @CookieValue 注解,一行搞定,推荐日常使用。request.getSession() 最基础;直接注入 HttpSession 参数利用了 Spring 的依赖注入能力;@SessionAttribute 最简洁,适合确定属性名存在的场景。request.getHeader() 是原生 API,@RequestHeader 是 Spring 封装的注解。重点理解:Controller 方法参数中的
HttpServletRequest、HttpSession等对象,都是由 Spring 框架在调用方法前自动创建并注入的——开发者只管声明参数,框架负责“送货上门”。
| 对比维度 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端浏览器 | 服务端(内存/Redis/数据库) |
| 存储容量 | 单个 ≤ 4KB,单域名总数约 20 个 | 理论上无限制,取决于服务器 |
| 安全性 | 明文存储,可被篡改或伪造 | 数据存于服务端,安全性高 |
| 生命周期 | 可设置过期时间,关闭浏览器不失效 | 默认 30 分钟无操作失效 |
| 性能影响 | 随每次请求发送,占用带宽 | 大量用户时占用服务器内存 |
| 关联方式 | 通过 JSESSIONID 与 Session 关联 | 以 SessionID 为唯一标识 |
关键要点:
;jsessionid=xxx(但现已不推荐,安全性差)。这是 Spring 开发中最容易被忽略的细节之一:
// 无参 / 参数为 true(默认行为) HttpSession session = request.getSession(); // 等价于: HttpSession session = request.getSession(true); // 行为:根据 Cookie 中的 JSESSIONID 查找 Session // → 找到了 → 返回已有 Session // → 没找到 → 新建一个 Session 并返回 // 参数为 false HttpSession session = request.getSession(false); // 行为:根据 JSESSIONID 查找 Session // → 找到了 → 返回已有 Session // → 没找到 → 返回 null(不创建!)
我们看看源码:

Returns the current session associated with this request, or if the request does not have a session, creates one.
实用场景:用户已登录才需要 Session 的场景,建议用
getSession(false)判断是否已有会话,避免为匿名访问者创建大量无用 Session 浪费内存。
Spring MVC 中,Controller 方法返回字符串时有两种解读方式,取决于是否加了 @ResponseBody。这里先说“不加”的情况——即返回的是视图路径。
假设类上标注了 @RequestMapping("/return"):
@Controller
@RequestMapping("/return")
public class ReturnController {
@RequestMapping("/returnPage")
public String returnPage() {
return "/aaa/index.html"; // 注意:以 "/" 开头
}
}
| 返回值写法 | 含义 | 实际访问路径 | 说明 |
|---|---|---|---|
return "index.html" | 相对路径 | /return/index.html | 拼接类上的路径 /return |
return "/index.html" | 绝对根路径 | /index.html | 忽略类上路径,直接从根开始 |
return "/aaa/index.html" | 绝对路径 | /aaa/index.html | 直接定位到 static/aaa/index.html |
Spring Boot 默认首页规则:
访问 http://127.0.0.1:8080 时,Spring Boot 会自动匹配 static/ 目录下的以下文件(按优先级):
index.htmlindex.htmindex.jsp所以把 index.html 放到 src/main/resources/static/ 下,无需任何 Controller 即可直接访问。
@ResponseBody 是理解 Spring MVC 的另一个关键注解。它的作用就一句话:把返回值当成“数据”直接写给浏览器,而不是当成“页面路径”去找模板。
@Controller
@RequestMapping("/return")
public class ReturnController {
// 不加 @ResponseBody → 返回值是视图路径
@RequestMapping("/returnPage")
public String returnPage() {
return "/aaa/index.html"; // 去找 /aaa/index.html 页面
}
// 加了 @ResponseBody → 返回值是原始数据
@ResponseBody
@RequestMapping("/returnData")
public String returnData() {
return "/aaa/index.html"; // 浏览器直接显示字符串 "/aaa/index.html"
}
}
两段代码返回值一模一样,但加不加 @ResponseBody,行为截然不同:
static/ 下找 aaa/index.html 模板渲染后返回;/aaa/index.html。注解的作用范围:
| 标注位置 | 影响范围 | 示例 |
|---|---|---|
| 类上 | 该类所有方法均返回数据 | @ResponseBody + @Controller = @RestController |
| 方法上 | 仅当前方法返回数据 | 类中可混合使用视图和数据返回 |
对象自动转 JSON:
@ResponseBody
@RequestMapping("/returnJSON")
public Person returnJSON() {
Person person = new Person();
person.setName("zmt");
person.setAge(18);
return person; // 自动序列化为 {"name":"zmt","age":18}
}
当返回值是实体类或数组时,Spring 会调用 Jackson 等序列化库自动转为 JSON 字符串,Content-Type 也会自动设为 application/json。这是前后端分离开发中最常用的模式。
返回 HTML 字符串也能被浏览器解析:
@ResponseBody
@RequestMapping("/returnHTML")
public String returnHTML() {
return "hello world
"; // 浏览器会自动渲染为一级标题
}
一句话总结:
@Controller管“走哪条路”,@ResponseBody管“输出什么”。而@RestController=@Controller+@ResponseBody,省去在每个方法上加注解的麻烦。
@RequestMapping 是 Spring MVC 中最核心的请求映射注解,功能远不止“写个 URL 路径”那么简单。它拥有丰富的属性,可以精确控制请求匹配条件。
| 属性 | 说明 | 使用场景 |
|---|---|---|
value / path | 互为别名,指定映射 URL | 基础路径映射 |
method | 限定请求方式(GET、POST、PUT、DELETE 等) | RESTful 风格接口 |
consumes | 限定请求的 Content-Type | 要求客户端以特定格式传参 |
produces | 指定响应的 Content-Type | 控制返回格式与编码 |
params | 请求必须携带指定参数 | 条件路由、版本控制 |
headers | 请求必须携带指定请求头 | 按客户端类型分发 |
produces 实战示例:
// 传统方式:手动设置 Content-Type 和编码
@ResponseBody
@RequestMapping("/setContentType")
public String setContentType(HttpServletResponse response) {
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
return "hello world
";
}
// 优雅方式:用 produces 属性一行搞定
@ResponseBody
@RequestMapping(value = "/setContentType2", produces = "text/plain;charset=UTF-8")
public String setContentType2() {
return "hello world
"; // 浏览器不解析,原样输出 HTML 源码
}
// 返回 JSON 格式数据
@ResponseBody
@RequestMapping(value = "/setContentType3", produces = "application/json")
public String setContentType3() {
return "{"name":"zmt","age":18}";
}
对比说明:
produces = "text/plain"时,标签会原样输出;而默认的text/html下,浏览器会将其渲染为大标题。控制好Content-Type,才能确保前端正确解析你的返回内容。
在 Web 开发领域,接口(API,Application Programming Interface) 指的是:客户端与服务器之间约定的通信规则——包括可以发起哪些 HTTP 请求、每个请求需要什么参数、返回什么格式的数据。
需要明确区分的是:这里的“接口”与 Java 中的 interface 关键字完全是两个概念。Java interface 是代码层面的抽象契约;Web API 是应用层通信协议,是前后端协作的“合同”。
在前后端分离架构下,前端和后端是两个独立的项目,可能由不同团队、甚至使用完全不同的技术栈开发。它们唯一的交集就是 HTTP 接口:

接口约定的典型内容:
POST /api/user/login{"username":"zmt", "password":"123456"}{"code":200, "msg":"success", "data":{"token":"xxx"}}前端按这个约定发请求,后端按这个约定返回数据,双方各自独立开发、独立测试,最后联调时只要“合同”没问题,就能无缝对接。
接口文档就是这份“合同”的书面记录。它描述了每个接口的:
常见的接口文档工具:Swagger / Knife4j、Apifox、YApi。对团队而言,接口文档是比口头沟通可靠一百倍的协作方式——先定接口,再写代码,是现代软件开发的标准流程。
在整个 Cookie/Session 机制和 Spring MVC 的返回值处理中,有两个贯穿始终的对象:
| 对象 | 职责 | 常用方法 |
|---|---|---|
| HttpServletRequest | 封装请求的所有信息 | getCookies()、getSession()、getParameter()、getHeader() |
| HttpServletResponse | 控制响应的所有行为 | addCookie()、setStatus()、setHeader()、setContentType() |
代码示例:
// 设置响应状态码
@ResponseBody
@RequestMapping("/setStatus")
public String setStatus(HttpServletResponse response) {
response.setStatus(404); // 返回 404 状态码
return "设置状态码成功";
}
// 设置响应头
@ResponseBody
@RequestMapping("/setHeader")
public String setHeader(HttpServletResponse response) {
response.setHeader("name", "zmt"); // 自定义响应头
return "设置响应头成功";
}

一句话记忆:HttpServletRequest 是“别人发来了什么”(只读),HttpServletResponse 是“我要回复什么”(只写)。二者分工明确,共同完成一次完整的 HTTP 交互。
本文从 HTTP 无状态协议的根本矛盾出发,系统梳理了 Cookie 与 Session 的设计动机、交互流程、代码实战和核心区别,同时深入讲解了 Spring MVC 中页面跳转路径规则、@ResponseBody 注解机制、@RequestMapping 属性运用,以及前后端接口(API)的概念和协作方式。
整条知识线的关联可以这样串起来:HTTP 无状态 → 需要识别用户 → Cookie 存令牌、Session 存数据 → request.getSession() 获取会话 → Controller 处理业务 → @ResponseBody 决定返回数据还是页面 → produces 控制响应格式 → 最终通过 HttpServletResponse 把结果送回客户端。
request.getSession() 无参默认创建新 Session,getSession(false) 查不到返回 null,要根据业务场景选择。@Controller 返回字符串 = 视图路径,@Controller + @ResponseBody 返回字符串 = 原始数据。@RestController = @Controller + @ResponseBody,是 RESTful 接口开发的标准注解。@RequestMapping 的 produces 属性可以优雅地设置响应 Content-Type 和编码,替代传统的 response.setContentType()。HttpServletRequest、HttpSession 等对象由框架自动创建并注入。Q1:@RestController 和 @Controller 到底怎么选?
@RestController;@Controller;@Controller,数据返回方法上加 @ResponseBody。Q2:produces 和 response.setContentType() 同时使用会怎样?
produces 本质上是声明式的 setContentType(),但后者的优先级更高。如果代码中调用了 response.setContentType("text/plain"),即便 produces 设了 application/json,最终的 Content-Type 也是 text/plain。建议二选一,优先使用 produces(更 Spring 化、更简洁)。
Q3:Session 和 Cookie 的关系是强制的吗?
不是。Cookie 可以独立存储普通数据(如“7 天内免登录”的 Token),不必放 SessionID;SessionID 也可以不走 Cookie 而走 URL 拼接(虽然不推荐)。二者常搭配使用,但不是强制绑定。
本文所有代码基于 Spring Boot 3.x + JDK 17 编写,完整可运行。如果你跟着敲一遍所有示例,Cookie/Session 机制和 Spring MVC 的返回值处理就基本吃透了。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8