当前位置: 代码网 > it编程>编程语言>Java > Cookie 与 Session 及 Spring MVC 返回值技术详解(完整交互流程)

Cookie 与 Session 及 Spring MVC 返回值技术详解(完整交互流程)

2026年08月04日 Java 我要评论
一、cookie 与 session 核心知识点1.1 http 无状态特性http(hypertext transfer protocol)是无状态协议。所谓"无状态",指的是协

一、cookie 与 session 核心知识点

1.1 http 无状态特性

http(hypertext transfer protocol)是无状态协议。所谓"无状态",指的是协议本身不会记录任何历史请求信息——服务器处理完一次请求后,就"忘记"了这次请求是谁发的。下次同一客户端再发请求,服务器依然"素不相识"。

打个比方:你打客服电话,每次接通都是不同的接线员,你必须把问题从头到尾重新说一遍。

然而实际业务中,"记住用户"是刚需——登录状态要保持、购物车不能刷新就清空、表单要跨页面填写。正是这种业务有状态协议无状态之间的矛盾,催生了 cookie + session 机制。

1.2 通俗类比:医院就诊卡与档案

理解 cookie 与 session 最好的方式,是类比就诊流程:

概念类比存储位置存放内容安全性
cookie就诊卡 / 学生证客户端(浏览器)仅存放唯一标识(sessionid)低,本地可伪造
session医院档案 / 学校学籍服务端(服务器内存)完整用户隐私数据高,无法伪造

完整就诊流程:

  1. 首次挂号:出示身份证(账号密码),医院给你一张就诊卡,同时在医院内部系统建立你的档案;
  2. 后续就诊:每次刷卡,医生就能调出你的全部历史病历;
  3. 退号注销:销毁就诊卡与档案的绑定关系。

对应到 web 开发:

  1. 首次登录:校验账号密码,服务端创建 session,分配唯一 sessionid
  2. 下发令牌:通过 set-cookie 响应头将 sessionid 存入浏览器 cookie;
  3. 后续请求:浏览器自动携带 cookie,服务端提取 sessionid,匹配对应用户的 session;
  4. 注销:清除 session 并删除客户端 cookie,解除绑定。

1.3 cookie 与 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;
    }
}

代码思路解析:

  • cookie 的读取有两种姿势:传统 request.getcookies() 返回数组需要手动遍历,不够优雅;spring 提供了 @cookievalue 注解,一行搞定,推荐日常使用。
  • session 的读取有三种方式:request.getsession() 最基础;直接注入 httpsession 参数利用了 spring 的依赖注入能力;@sessionattribute 最简洁,适合确定属性名存在的场景。
  • 请求头的读取同理:request.getheader() 是原生 api,@requestheader 是 spring 封装的注解。

重点理解:controller 方法参数中的 httpservletrequesthttpsession 等对象,都是由 spring 框架在调用方法前自动创建并注入的——开发者只管声明参数,框架负责"送货上门"。

1.4 cookie 与 session 核心区别

对比维度cookiesession
存储位置客户端浏览器服务端(内存/redis/数据库)
存储容量单个 ≤ 4kb,单域名总数约 20 个理论上无限制,取决于服务器
安全性明文存储,可被篡改或伪造数据存于服务端,安全性高
生命周期可设置过期时间,关闭浏览器不失效默认 30 分钟无操作失效
性能影响随每次请求发送,占用带宽大量用户时占用服务器内存
关联方式通过 jsessionid 与 session 关联以 sessionid 为唯一标识

关键要点:

  1. 二者非强制绑定。cookie 可以独立存储普通业务数据(如主题偏好),不必存放 sessionid;sessionid 也不一定要通过 cookie 传递,可以在 url 后拼接 ;jsessionid=xxx(但现已不推荐,安全性差)。
  2. sessionid 是桥梁。它把"客户端的小纸条"和"服务端的档案柜"连接了起来。
  3. 服务重启数据丢失。默认 session 存储在服务器内存中,一旦服务重启,所有登录态全部丢失。生产环境通常将 session 外置到 redis 等中间件来解决这个问题。

1.5request.getsession()参数规则(容易踩坑)

这是 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 页面跳转与返回值

2.1 路径书写规则(@controller场景,返回视图页面)

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/ 目录下的以下文件(按优先级):

  1. index.html
  2. index.htm
  3. index.jsp

所以把 index.html 放到 src/main/resources/static/ 下,无需任何 controller 即可直接访问。

2.2@responsebody注解

@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,行为截然不同:

  • 不加:spring 去 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 "<h1>hello world</h1>";   // 浏览器会自动渲染为一级标题
}

一句话总结@controller 管"走哪条路",@responsebody 管"输出什么"。而 @restcontroller = @controller + @responsebody,省去在每个方法上加注解的麻烦。

2.3@requestmapping注解属性详解

@requestmapping 是 spring mvc 中最核心的请求映射注解,功能远不止"写个 url 路径"那么简单。它拥有丰富的属性,可以精确控制请求匹配条件。

属性说明使用场景
value / path互为别名,指定映射 url基础路径映射
method限定请求方式(getpostputdelete 等)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 "<h1>hello world</h1>";
}
// 优雅方式:用 produces 属性一行搞定
@responsebody
@requestmapping(value = "/setcontenttype2", produces = "text/plain;charset=utf-8")
public string setcontenttype2() {
    return "<h1>hello world</h1>";   // 浏览器不解析,原样输出 html 源码
}
// 返回 json 格式数据
@responsebody
@requestmapping(value = "/setcontenttype3", produces = "application/json")
public string setcontenttype3() {
    return "{\"name\":\"zmt\",\"age\":18}";
}

对比说明produces = "text/plain" 时,<h1> 标签会原样输出;而默认的 text/html 下,浏览器会将其渲染为大标题。控制好 content-type,才能确保前端正确解析你的返回内容。

三、前后端接口(api)概念

3.1 什么是接口(api)

在 web 开发领域,接口(api,application programming interface) 指的是:客户端与服务器之间约定的通信规则——包括可以发起哪些 http 请求、每个请求需要什么参数、返回什么格式的数据。

需要明确区分的是:这里的"接口"与 java 中的 interface 关键字完全是两个概念。java interface 是代码层面的抽象契约;web api 是应用层通信协议,是前后端协作的"合同"。

3.2 前后端分离与接口约定

在前后端分离架构下,前端和后端是两个独立的项目,可能由不同团队、甚至使用完全不同的技术栈开发。它们唯一的交集就是 http 接口

接口约定的典型内容:

  • 请求路径:post /api/user/login
  • 请求参数:{"username":"zmt", "password":"123456"}
  • 响应格式:{"code":200, "msg":"success", "data":{"token":"xxx"}}

前端按这个约定发请求,后端按这个约定返回数据,双方各自独立开发、独立测试,最后联调时只要"合同"没问题,就能无缝对接。

3.3 接口文档

接口文档就是这份"合同"的书面记录。它描述了每个接口的:

  • url 路径和请求方式
  • 请求参数(名称、类型、是否必填、示例值)
  • 响应数据结构(字段含义、类型、示例)
  • 错误码说明

常见的接口文档工具:swagger / knife4j、apifox、yapi。对团队而言,接口文档是比口头沟通可靠一百倍的协作方式——先定接口,再写代码,是现代软件开发的标准流程。

补充:request 与 response 对象

在整个 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 把结果送回客户端。

核心知识点复盘

  1. http 是无状态协议,cookie + session 是弥补无状态缺陷的两大机制。
  2. cookie 存客户端,session 存服务端,sessionid 是二者之间的唯一桥梁。
  3. cookie 可伪造,session 不可伪造——凡是涉及权限判断的场景,只能信任服务端 session 中的数据。
  4. request.getsession() 无参默认创建新 session,getsession(false) 查不到返回 null,要根据业务场景选择。
  5. @controller 返回字符串 = 视图路径,@controller + @responsebody 返回字符串 = 原始数据。
  6. @restcontroller = @controller + @responsebody,是 restful 接口开发的标准注解。
  7. @requestmappingproduces 属性可以优雅地设置响应 content-type 和编码,替代传统的 response.setcontenttype()
  8. spring 自动注入机制:方法参数中的 httpservletrequesthttpsession 等对象由框架自动创建并注入。
  9. session 默认存内存,服务重启全部丢失,生产环境应外置到 redis。
  10. 接口就是前后端之间的"合同",先定接口再写代码是现代开发的标准实践。

常见问题 / 避坑指南

q1:@restcontroller@controller 到底怎么选?

  • 前后端分离项目:接口只返回 json 数据 → 用 @restcontroller
  • 传统 mvc 项目:需要跳转页面、使用模板引擎(thymeleaf、jsp)→ 用 @controller
  • 混合场景:同一类中既有页面跳转又需要返回数据 → 类上 @controller,数据返回方法上加 @responsebody

q2:producesresponse.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 的返回值处理就基本吃透了。

到此这篇关于cookie 与 session 及 spring mvc 返回值技术详解(完整交互流程)的文章就介绍到这了,更多相关cookie 与 session 及 spring mvc 返回值内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com