java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Session原理、应用与实践

Java Session原理、应用与实践全面指南(含Spring Boot实战)

作者:索荣荣

这篇文章主要介绍了Java Session原理、应用与实践全面指南的相关资料,本文深入探讨了Web应用中的Session管理技术,从基础原理到分布式环境下的挑战及解决方案,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

这是一份详尽、实用且权威的 Java Session 全面指南,涵盖了从基础概念到在 Spring Boot 中的应用,并包含可直接运行的代码示例。

1. 理解会话(Session)

1.1 为什么需要会话?

HTTP 协议本身是无状态的。这意味着服务器在处理完一个客户端请求后,就会“忘记”这个客户端。当下一个请求到来时,服务器无法直接知道这个新请求是否来自之前同一个用户。

例如:

如果 HTTP 是无状态的,服务器就无法将这些操作关联到同一个用户 A 身上。这就需要一种机制在多个请求之间保持用户的状态信息。这就是 Session(会话) 要解决的问题。

1.2 会话的本质是什么?

会话(Session) 是服务器端用来跟踪特定用户(或浏览器实例)的一种机制。服务器为每个用户(或浏览器会话)创建一个唯一的、服务器端存储的会话对象。这个对象就像一个临时的、用户专属的存储空间。

关键点:

1.3 会话与 Cookie 的关系与区别

会话的实现通常依赖于 CookieURL 重写 来传递 Session ID。

1.4 会话的生命周期

  1. 创建: 通常发生在用户首次与服务器交互,且程序调用 request.getSession()request.getSession(true) 时。如果请求中带有有效的 Session ID,则获取现有会话,不会创建新会话。
  2. 活动: 用户持续与服务器交互,Session ID 通过 Cookie 或 URL 在每个请求中传递。服务器在此期间可以向会话中添加、读取、修改或移除属性。
  3. 销毁:
    • 超时销毁 (Inactive Timeout): 最常见的销毁方式。服务器配置一个超时时间(例如 30 分钟)。如果用户在最后一次请求后,超过这个时间没有新的请求,服务器会自动销毁该会话,释放资源。
    • 显式销毁: 程序调用 session.invalidate() 方法主动销毁会话。通常在用户“注销”时调用。
    • 应用关闭/崩溃: 如果会话存储在服务器内存中,应用重启或崩溃会导致所有内存中的会话丢失。如果使用持久化存储(如数据库、Redis),会话可以存活得更久。
    • 浏览器关闭: 如果用于传递 Session ID 的 Cookie 是会话 Cookie(没有设置 max-ageexpires),那么关闭浏览器会删除这个 Cookie,导致服务器端的会话虽然还存在(直到超时),但用户再次访问时无法通过原来的 Session ID 找到它(服务器会创建一个新会话)。如果 Cookie 设置了有效期,关闭浏览器再打开,Cookie 仍然存在,可以找到原来的会话。

2. Java Web 中的会话管理:HttpSession

Java Servlet API 提供了 javax.servlet.http.HttpSession 接口来实现会话管理。

2.1HttpSession接口详解

HttpSession 接口定义了操作会话的核心方法:

2.2 创建和获取HttpSession

在 Servlet 的 doGet, doPost 等方法中,通过 HttpServletRequest 对象获取会话:

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class MyServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 获取当前请求的会话。如果不存在,则创建一个新的会话。
        HttpSession session = request.getSession(); // 等同于 request.getSession(true)

        // 如果只是想获取现有会话,而不想创建新会话(例如检查用户是否已登录)
        HttpSession existingSession = request.getSession(false);
        if (existingSession != null) {
            // 存在现有会话
        } else {
            // 不存在现有会话
        }
    }
}

2.3 在会话中存储数据

使用 setAttribute(String name, Object value) 方法将数据存储在会话中。value 应该是可序列化的对象(如果考虑集群或持久化存储)。

session.setAttribute("username", "张三"); // 存储字符串
session.setAttribute("cart", myShoppingCart); // 存储自定义对象 (myShoppingCart 应实现 Serializable)
session.setAttribute("loginTime", new Date()); // 存储 Date 对象

2.4 从会话中获取数据

使用 getAttribute(String name) 方法从会话中检索数据。需要强制转换为正确的类型。

String username = (String) session.getAttribute("username");
ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
Date loginTime = (Date) session.getAttribute("loginTime");

2.5 使会话失效

通常在用户注销时调用 invalidate() 方法。这会立即销毁会话对象,并移除其中存储的所有属性。服务器通常会发送一个过期时间为 0 的 JSESSIONID Cookie,指示浏览器删除它。

protected void doPost(HttpServletRequest request, HttpServletResponse response) {
    // 处理注销逻辑...
    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate(); // 销毁会话
    }
    // 重定向到登录页或首页...
}

2.6 监听会话事件:HttpSessionListener

实现 HttpSessionListener 接口可以监听会话的创建和销毁事件。

import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

public class MySessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        // 当一个新的 HttpSession 被创建时调用
        System.out.println("Session created: " + se.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        // 当一个 HttpSession 即将被销毁(超时或 invalidate)时调用
        System.out.println("Session destroyed: " + se.getSession().getId());
    }
}

需要在 web.xml 中注册监听器:

<listener>
    <listener-class>com.yourpackage.MySessionListener</listener-class>
</listener>

或者在支持注解的 Servlet 容器中(如 Tomcat 7+),使用 @WebListener

@WebListener
public class MySessionListener implements HttpSessionListener {
    // ... 实现方法 ...
}

2.7 监听会话属性 事件:HttpSessionAttributeListener

实现 HttpSessionAttributeListener 接口可以监听会话中属性的添加、移除和替换事件。

import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class MySessionAttributeListener implements HttpSessionAttributeListener {

    @Override
    public void attributeAdded(HttpSessionBindingEvent event) {
        // 当有属性被添加到会话中时调用
        System.out.println("Attribute added: " + event.getName() + " = " + event.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent event) {
        // 当有属性从会话中被移除时调用
        System.out.println("Attribute removed: " + event.getName());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent event) {
        // 当会话中已有的属性被新值替换时调用
        System.out.println("Attribute replaced: " + event.getName() + " (old value: " + event.getValue() + ")");
        // 注意:event.getValue() 返回的是被替换的旧值!
    }
}

注册方式与 HttpSessionListener 相同(web.xml@WebListener)。

2.8 会话超时配置

会话超时时间可以通过多种方式配置:

3. Spring Boot 中的会话管理

Spring Boot 简化了 Java Web 开发,包括会话管理。它自动配置了底层的 Servlet 容器(如 Tomcat, Jetty),并提供了便捷的注解来使用会话。

3.1 Spring Boot 对会话的自动化配置

3.2 在 Controller 中使用HttpSession

在 Spring MVC 的 @Controller@RestController 中,可以直接将 HttpSession 作为方法参数注入:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import javax.servlet.http.HttpSession;

@Controller
public class MyController {

    @GetMapping("/storeInSession")
    public String storeData(HttpSession session) {
        session.setAttribute("key", "value stored in Spring Boot session");
        return "redirect:/result";
    }

    @GetMapping("/getFromSession")
    public String getData(HttpSession session) {
        String value = (String) session.getAttribute("key");
        // ... 使用 value ...
        return "resultPage";
    }
}

3.3 使用@SessionAttributes注解

@SessionAttributes 注解用于在 Controller 级别 声明模型属性(Model Attributes)应该存储在会话中。这些属性会在多个请求之间保持(跨越同一个 Controller 的多个方法)。

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

@Controller
@RequestMapping("/multiStepForm")
@SessionAttributes({"step1Data", "step2Data"}) // 指定哪些模型属性名要存入会话
public class MultiStepFormController {

    @ModelAttribute("step1Data") // 初始化模型属性 (可选)
    public Step1Data initStep1Data() {
        return new Step1Data();
    }

    @GetMapping("/step1")
    public String showStep1Form(@ModelAttribute("step1Data") Step1Data step1Data) {
        return "step1Form";
    }

    @PostMapping("/step1")
    public String processStep1(@ModelAttribute("step1Data") Step1Data step1Data) {
        // 处理 step1 数据。因为 step1Data 在 @SessionAttributes 中,它会自动保存到会话
        return "redirect:/multiStepForm/step2";
    }

    @GetMapping("/step2")
    public String showStep2Form(@ModelAttribute("step2Data") Step2Data step2Data) {
        return "step2Form";
    }

    @PostMapping("/step2")
    public String processStep2(@ModelAttribute("step2Data") Step2Data step2Data,
                               @ModelAttribute("step1Data") Step1Data step1Data, // 从会话获取 step1Data
                               SessionStatus sessionStatus) {
        // 处理 step2 数据,并结合 step1Data 完成整个表单
        // ...

        // 清除会话中由 @SessionAttributes 存储的属性 (可选)
        sessionStatus.setComplete(); // 清除 step1Data, step2Data
        return "confirmationPage";
    }
}

注意:

3.4 使用@SessionAttribute注解

@SessionAttribute 注解用于在方法参数上,指示参数值应该从会话中获取,而不是从请求参数或模型中获取。

@GetMapping("/viewCart")
public String viewCart(@SessionAttribute("shoppingCart") ShoppingCart cart, Model model) {
    model.addAttribute("cartItems", cart.getItems());
    return "cartView";
}

这个注解通常用于访问那些不是通过 @SessionAttributes 声明,而是由你直接通过 session.setAttribute 或其他方式存储在会话中的属性。

3.5 在服务层或工具类中获取HttpSession

有时需要在非 Controller 的组件(如 Service, Util)中访问会话。可以通过 HttpServletRequest 来间接获取 HttpSession

import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@Service
public class MyService {

    public void doSomethingWithSession() {
        // 获取当前请求的 HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取 HttpSession (谨慎使用,确保当前线程在处理 Web 请求)
        HttpSession session = request.getSession(false);
        if (session != null) {
            Object value = session.getAttribute("key");
            // ... 使用 value ...
        }
    }
}

重要提示: 这种方式依赖于 RequestContextHolder,它使用 ThreadLocal 来存储当前请求的属性。只有在处理 Web 请求的线程中调用这个方法才有效(例如,在 Controller 调用的 Service 方法中)。在异步任务、定时任务或其他非请求线程中调用会失败(RequestContextHolder 获取不到请求)。

3.6 Spring Boot 中的会话超时配置

在 Spring Boot 的配置文件(application.propertiesapplication.yml)中设置:

# application.properties
server.servlet.session.timeout=30m # 设置会话超时为 30 分钟。单位: s(秒), m(分钟), h(小时), d(天)
# application.yml
server:
  servlet:
    session:
      timeout: 30m

这个配置最终会调用 session.setMaxInactiveInterval()。它覆盖了 web.xml 中的设置。

4. 最佳实践与注意事项

4.1 存储什么类型的数据?

4.2 会话大小与性能考虑

4.3 安全性考虑

4.4 分布式环境下的会话管理 (简介)

当应用部署在多台服务器(集群)时,用户的不同请求可能被负载均衡器分发到不同的服务器实例。如果会话存储在单个服务器的内存中,用户后续请求如果落到另一台服务器,将无法找到之前的会话(导致用户需要重新登录)。

解决方案:

4.5 替代方案 (Token, JWT 等)

对于 RESTful API 或前后端分离架构,基于 Cookie/Session 的状态管理可能不再适用。常用替代方案包括:

5. 实战案例:可直接运行的 Spring Boot 应用

以下案例假设您已创建一个基本的 Spring Boot Web 项目 (使用 Spring Initializr, 选择 Web -> Spring Web 依赖)。

5.1 案例一:用户登录状态管理

5.1.1 场景描述

实现一个简单的用户登录功能。用户输入用户名密码(模拟验证),登录成功后记录用户信息到 Session。后续访问需要登录才能查看的页面时,检查 Session 中是否存在用户信息。提供注销功能销毁 Session。

5.1.2 核心代码实现

  1. User 类 (简化版):

    package com.example.sessiondemo.model;
    import java.io.Serializable;
    public class User implements Serializable { // 实现 Serializable 以备分布式存储
        private String username;
        // 省略构造函数、getter、setter
    }
    
  2. LoginController

    package com.example.sessiondemo.controller;
    import com.example.sessiondemo.model.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import javax.servlet.http.HttpSession;
    
    @Controller
    public class LoginController {
    
        // 模拟用户验证
        private boolean isValidUser(String username, String password) {
            // 实际应用中应查询数据库
            return "admin".equals(username) && "123456".equals(password);
        }
    
        @GetMapping("/login")
        public String loginPage() {
            return "login"; // 返回 login.html 视图
        }
    
        @PostMapping("/login")
        public String login(@RequestParam String username,
                            @RequestParam String password,
                            HttpSession session,
                            Model model) {
            if (isValidUser(username, password)) {
                // 登录成功,创建 User 对象存入 Session
                User user = new User();
                user.setUsername(username);
                session.setAttribute("user", user);
                return "redirect:/dashboard"; // 重定向到仪表盘
            } else {
                model.addAttribute("error", "Invalid username or password");
                return "login"; // 返回登录页并显示错误
            }
        }
    
        @GetMapping("/dashboard")
        public String dashboard(HttpSession session, Model model) {
            User user = (User) session.getAttribute("user");
            if (user == null) {
                // 用户未登录,重定向到登录页
                return "redirect:/login";
            }
            model.addAttribute("username", user.getUsername());
            return "dashboard"; // 返回 dashboard.html 视图
        }
    
        @GetMapping("/logout")
        public String logout(HttpSession session) {
            // 使会话失效
            session.invalidate();
            return "redirect:/login";
        }
    }
    
  3. 视图模板 (Thymeleaf 示例):

    • src/main/resources/templates/login.html
      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <title>Login</title>
      </head>
      <body>
          <h1>Login</h1>
          <div th:if="${error}" th:text="${error}" style="color: red;"></div>
          <form th:action="@{/login}" method="post">
              Username: <input type="text" name="username">
              Password: <input type="password" name="password">
              <button type="submit">Login</button>
          </form>
      </body>
      </html>
    • src/main/resources/templates/dashboard.html
      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <title>Dashboard</title>
      </head>
      <body>
          <h1>Welcome, <span th:text="${username}">User</span>!</h1>
          <p>This is your dashboard.</p>
          <a th:href="@{/logout}" rel="external nofollow" >Logout</a>
      </body>
      </html>

5.1.3 完整可运行代码结构

src/
  main/
    java/
      com.example.sessiondemo/
        SessionDemoApplication.java // Spring Boot 主类
        controller/
          LoginController.java
        model/
          User.java
    resources/
      application.properties
      templates/
        login.html
        dashboard.html

5.2 案例二:购物车功能实现

5.2.1 场景描述

实现一个简单的购物车。用户可以将商品加入购物车,查看购物车内容。购物车信息存储在用户的 Session 中。

5.2.2 核心代码实现

  1. Product 类:

    package com.example.sessiondemo.model;
    import java.io.Serializable;
    public class Product implements Serializable {
        private Long id;
        private String name;
        private double price;
        // 省略构造函数、getter、setter
    }
    
  2. CartItem 类 (购物车项):

    package com.example.sessiondemo.model;
    import java.io.Serializable;
    public class CartItem implements Serializable {
        private Product product;
        private int quantity;
        // 省略构造函数、getter、setter
        public double getTotalPrice() {
            return product.getPrice() * quantity;
        }
    }
    
  3. ShoppingCart 类 (购物车):

    package com.example.sessiondemo.model;
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    public class ShoppingCart implements Serializable {
        private List<CartItem> items = new ArrayList<>();
    
        public void addItem(Product product, int quantity) {
            for (CartItem item : items) {
                if (item.getProduct().getId().equals(product.getId())) {
                    item.setQuantity(item.getQuantity() + quantity);
                    return;
                }
            }
            items.add(new CartItem(product, quantity));
        }
    
        public List<CartItem> getItems() {
            return items;
        }
    
        public double getTotalAmount() {
            return items.stream().mapToDouble(CartItem::getTotalPrice).sum();
        }
    
        public void clear() {
            items.clear();
        }
    }
    
  4. ProductController (模拟商品列表):

    package com.example.sessiondemo.controller;
    import com.example.sessiondemo.model.Product;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import java.util.Arrays;
    import java.util.List;
    
    @Controller
    public class ProductController {
    
        @GetMapping("/products")
        public String listProducts(Model model) {
            // 模拟商品数据
            List<Product> products = Arrays.asList(
                new Product(1L, "Product A", 10.0),
                new Product(2L, "Product B", 20.0),
                new Product(3L, "Product C", 30.0)
            );
            model.addAttribute("products", products);
            return "productList";
        }
    }
    
  5. CartController

    package com.example.sessiondemo.controller;
    import com.example.sessiondemo.model.CartItem;
    import com.example.sessiondemo.model.Product;
    import com.example.sessiondemo.model.ShoppingCart;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import javax.servlet.http.HttpSession;
    
    @Controller
    public class CartController {
    
        @PostMapping("/cart/add")
        public String addToCart(@RequestParam Long productId,
                                @RequestParam int quantity,
                                HttpSession session) {
            // 实际应用中应根据 productId 从数据库获取 Product
            Product product = new Product(productId, "Product " + productId, productId * 10.0); // 模拟
    
            // 获取或创建购物车
            ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
            if (cart == null) {
                cart = new ShoppingCart();
                session.setAttribute("cart", cart);
            }
    
            cart.addItem(product, quantity);
            return "redirect:/cart"; // 重定向到购物车页面
        }
    
        @GetMapping("/cart")
        public String viewCart(HttpSession session, Model model) {
            ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
            model.addAttribute("cart", cart);
            return "cartView";
        }
    
        @PostMapping("/cart/clear")
        public String clearCart(HttpSession session) {
            ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
            if (cart != null) {
                cart.clear();
            }
            return "redirect:/cart";
        }
    }
    
  6. 视图模板:

    • src/main/resources/templates/productList.html
      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <title>Products</title>
      </head>
      <body>
          <h1>Product List</h1>
          <table>
              <tr th:each="product : ${products}">
                  <td th:text="${product.name}"></td>
                  <td th:text="${product.price}"></td>
                  <td>
                      <form th:action="@{/cart/add}" method="post">
                          <input type="hidden" name="productId" th:value="${product.id}">
                          <input type="number" name="quantity" value="1" min="1">
                          <button type="submit">Add to Cart</button>
                      </form>
                  </td>
              </tr>
          </table>
          <a th:href="@{/cart}" rel="external nofollow" >View Cart</a>
      </body>
      </html>
      
    • src/main/resources/templates/cartView.html
      <!DOCTYPE html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <title>Shopping Cart</title>
      </head>
      <body>
          <h1>Your Shopping Cart</h1>
          <table th:if="${cart != null and !cart.items.empty}">
              <tr>
                  <th>Product</th>
                  <th>Price</th>
                  <th>Quantity</th>
                  <th>Total</th>
              </tr>
              <tr th:each="item : ${cart.items}">
                  <td th:text="${item.product.name}"></td>
                  <td th:text="${item.product.price}"></td>
                  <td th:text="${item.quantity}"></td>
                  <td th:text="${item.totalPrice}"></td>
              </tr>
              <tr>
                  <td colspan="3">Total Amount:</td>
                  <td th:text="${cart.totalAmount}"></td>
              </tr>
          </table>
          <p th:if="${cart == null or cart.items.empty}">Your cart is empty.</p>
          <form th:action="@{/cart/clear}" method="post" th:if="${cart != null and !cart.items.empty}">
              <button type="submit">Clear Cart</button>
          </form>
          <a th:href="@{/products}" rel="external nofollow" >Continue Shopping</a>
      </body>
      </html>
      

5.2.3 完整可运行代码结构

src/
  main/
    java/
      com.example.sessiondemo/
        SessionDemoApplication.java
        controller/
          LoginController.java // (可选,如果和案例一结合)
          ProductController.java
          CartController.java
        model/
          User.java // (可选)
          Product.java
          CartItem.java
          ShoppingCart.java
    resources/
      application.properties
      templates/
        login.html // (可选)
        dashboard.html // (可选)
        productList.html
        cartView.html

5.3 案例三:分布式会话管理 (使用 Spring Session + Redis)

5.3.1 场景描述与为什么需要分布式会话

假设应用部署在两个实例 (Instance A 和 Instance B) 上,由负载均衡器分发请求。用户 U1 在 Instance A 登录,Session 存储在 A 的内存中。用户 U1 的下一个请求被负载均衡器分发到 Instance B。Instance B 在自己的内存中找不到 U1 的 Session,导致 U1 需要重新登录。用户体验差。

解决方案: 使用 Spring Session 结合 Redis 实现分布式会话存储。所有服务器实例都从同一个 Redis 中读写会话数据。

5.3.2 依赖引入与配置

  1. 添加依赖 (pom.xml):

    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Session Data Redis -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <!-- Spring Data Redis Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. 配置 Redis 连接 (application.properties):

    # Redis 服务器地址
    spring.redis.host=localhost
    # Redis 服务器端口
    spring.redis.port=6379
    # Redis 密码 (如果设置了)
    # spring.redis.password=yourpassword
    # Redis 数据库索引 (默认 0)
    spring.redis.database=0
    # 配置 Spring Session 存储类型为 Redis
    spring.session.store-type=redis
    # (可选) 配置 Session 在 Redis 中的过期时间 (秒), 会覆盖 server.servlet.session.timeout
    # spring.session.redis.flush-mode=on_save
    # spring.session.redis.time-to-live=1800
  3. 主类启用 Spring Session (SessionDemoApplication.java):

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
    
    @SpringBootApplication
    @EnableRedisHttpSession // 启用 Spring Session 并指定使用 Redis 存储 HttpSession
    public class SessionDemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(SessionDemoApplication.class, args);
        }
    }
    

5.3.3 核心代码实现 (与普通 Session 使用方式一致)

关键点: Spring Session 透明地替换了底层的 HttpSession 实现。这意味着你之前使用 HttpSession 的代码(如案例一、案例二中的 LoginControllerCartController几乎不需要修改! 它们仍然通过 request.getSession() 获取 Session,通过 setAttribute/getAttribute 存取数据。Spring Session 负责将这些操作桥接到 Redis 存储。

5.3.4 完整可运行代码结构

在案例一或案例二代码结构的基础上:

  1. 添加上述依赖到 pom.xml
  2. 添加 Redis 配置到 application.properties
  3. 在主类上添加 @EnableRedisHttpSession
  4. 确保所有存储在 Session 中的对象 (User, ShoppingCart, CartItem, Product) 都实现 java.io.Serializable
  5. 安装并运行 Redis 服务器 (localhost:6379)。

测试:

  1. 启动两个 Spring Boot 应用实例 (设置不同的端口 server.port=8081, server.port=8082 在配置文件中)。
  2. 通过负载均衡器 (或直接访问不同端口) 测试登录、购物车功能。用户在一个实例上的操作(如登录、加购),在另一个实例访问时应能看到相同的状态。

总结

本指南详细介绍了 Java Session 的核心概念、HttpSession API 的使用、在 Spring Boot 中的集成方式、最佳实践以及分布式环境下的解决方案(Spring Session + Redis)。通过三个完整的实战案例,展示了 Session 在不同场景下的应用。希望这份指南能帮助您深入理解和有效地在项目中应用 Java Session。

到此这篇关于Java Session原理、应用与实践全面指南的文章就介绍到这了,更多相关Java Session原理、应用与实践内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文