Spring MVC(三)

**摘要:**本文系统介绍了 HTTP 无状态协议下的状态管理方案,重点讲解了 Cookie 和 Session 的工作原理、区别及在 Spring MVC 中的实际应用。文章首先通过医院就诊卡类比解释了令牌机制,然后详细阐述了 Session 的概念、作用、本质和工作流程。接着对比了 Cookie 与 Session 在存储位置、安全性、容量等方面的差异,并提供了获取 Cookie 和 Session 的多种方式(包括 HttpServletRequest、注解等)。最后通过运行示例演示了 Session 的设置与获取过程,并介绍了获取 HTTP Header 的方法以及 @RestController 与 @Controller 的区别与适用场景。

HTTP 协议本身是"无状态"协议,这意味着默认情况下,客户端与服务器之间的每次通信都是独立的,没有直接的关联关系。

"无状态"的含义:

默认情况下,HTTP 协议的客户端和服务器之间的本次通信与下次通信之间没有直接联系。每次请求都是独立的,服务器不会记住之前的请求信息。

但在实际开发中,我们经常需要知道请求之间的关联关系。例如,用户登录网站成功后,第二次访问时服务器需要能够识别该用户是否已经登录。

上述图中的"令牌"通常存储在 Cookie 字段中,用于在客户端保存用户状态信息。

医院就诊卡类比:

  1. 看病前先挂号,提供身份证后获得"就诊卡",相当于患者的"令牌"
  2. 后续检查、诊断、开药等操作,只需出示就诊卡即可识别身份
  3. 看完病后可以注销就诊卡,销毁身份与卡的关联(类似网站注销)
  4. 再次看病时,可以办理新的就诊卡,获得新的"令牌"
  • 建档 → 医院返回就诊卡
  • 看病 → 携带就诊卡

就诊卡 ≈ 令牌 ≈ Cookie

服务器端需要记录"令牌"信息及其对应的用户信息,这就是 Session 机制的作用。

理解 Session

什么是会话?

会话(Session)在计算机领域指客户端与服务器之间持续不断的请求-响应交互过程。服务器能够识别来自同一客户端的连续请求,从而维持状态信息。

客服电话类比:

每次拨打客服电话是一个会话,挂断电话会话结束。再次拨打是新的会话。如果长时间不说话(无新请求),会话也会自动结束。

Session 的作用

服务器同时处理大量请求,需要区分每个请求属于哪个用户(哪个会话)。Session 就是服务器为保存用户信息而创建的特殊对象。

Session 的本质

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

SessionId 与 Token:

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

Session 工作流程

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

**注意:**Session 默认保存在服务器内存中,重启服务器会导致 Session 数据丢失。生产环境通常需要持久化存储方案。

对比项 Cookie Session
存储位置 客户端(浏览器) 服务器端
安全性 较低(客户端可查看修改) 较高(服务器端存储)
存储容量 较小(通常 4KB) 较大(受服务器内存限制)
生命周期 可设置过期时间 会话结束或超时销毁
关联关系 通过 SessionId 关联,SessionId 通常存储在 Cookie 中

补充说明:

  1. Cookie 不仅可以存储 SessionId,还可以存储其他客户端数据
  2. SessionId 不一定通过 Cookie 传递,也可以通过 URL 参数等方式传递
  3. 两者经常配合使用,但并非必须同时使用

方式一:通过 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;
}
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 } }

使用建议:

  1. **传统 Web 应用:**使用 @Controller,配合视图模板(如 Thymeleaf、JSP)
  2. **前后端分离项目:**使用 @RestController,提供 RESTful API 接口
  3. **混合项目:**可以同时使用两者,@Controller 处理页面请求,@RestController 处理 API 请求

总结:@RestController 是 Spring 4.0 引入的便捷注解,专门用于构建 RESTful Web 服务。它简化了 API 开发,避免了在每个方法上重复添加 @ResponseBody 注解。

相关推荐
她说可以呀1 小时前
Spring-ai-alibaba视频生成
人工智能·spring·音视频
Leon-Ning Liu1 小时前
【真实经验分享】ORA-12516 排查实录:共享服务器模式下 SHARED_SERVER_SESSIONS 不足引发的监听故障
运维·服务器·oracle
用户3126874877201 小时前
Spring Boot 启动流程到底做了什么?从 main 到就绪的全链路源码拆解
spring boot
东方护航数据恢复(深圳)1 小时前
服务器硬盘黄灯/红灯故障排查与处理命令全指南_东方护航数据恢复深圳店
运维·服务器·chrome
爱码少年1 小时前
SSH密钥登录完整流程与原理详解
运维·ssh
会编程的吕洞宾1 小时前
Spring Boot 虚拟线程实战:从原理到生产环境
java·后端·spring
黑桃小柒72 小时前
Prompt 工程的“断舍离“:如何用更少的词让 AI Agent 更聪明
java·后端·spring
uncle_ll2 小时前
服务器选型、微调范式、训练优化与环境搭建
服务器·python·gpt·llm·nlp
一路向北North2 小时前
Spring AI(9) :解决百炼平台兼容性问题
java·人工智能·spring