这是一个非常经典且重要的应用场景,适合理解 Session 的工作原理。

核心思想
-
会话跟踪:HTTP 协议是无状态的,服务器无法区分不同的用户请求是否来自同一个人。
HttpSession是服务器用来跟踪同一个用户在多次请求之间状态的一种机制,当用户第一次访问网站时,服务器会创建一个唯一的 Session ID,并通过 Cookie 将其发送给浏览器,之后浏览器每次请求都会带上这个 Cookie,服务器就能识别出是哪个用户的会话,从而从服务器内存中取出对应的 Session 对象。 -
购物车状态:购物车(商品列表、数量、总价等)是典型的“会话级”数据,它只对当前登录的(或匿名的)用户有效,一旦用户关闭浏览器或会话超时,购物车就应该清空,这完美地契合了
HttpSession的生命周期。 -
存储位置:购物车对象本身会作为
HttpSession的一个属性被存储在服务器内存中,我们只需要在 Session 中用一个唯一的键("cart")来引用这个购物车对象即可。
实现步骤
我们将分步实现一个基于 Session 的购物车。

步骤 1:准备数据模型 - Product (商品) 和 CartItem (购物车项)
我们需要定义商品和购物车项的 JavaBean。
Product.java
// Product.java
public class Product {
private int id;
private String name;
private double price;
// 构造器、Getter 和 Setter
public Product(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public int getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
@Override
public String toString() {
return "Product{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}';
}
}
CartItem.java 购物车项不仅仅是商品,它还包含了用户购买的数量。
// CartItem.java
public class CartItem {
private Product product;
private int quantity;
// 构造器、Getter 和 Setter
public CartItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public Product getProduct() { return product; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
// 计算小计
public double getSubtotal() {
return product.getPrice() * quantity;
}
@Override
public String toString() {
return "CartItem{" + "product=" + product + ", quantity=" + quantity + '}';
}
}
步骤 2:创建购物车逻辑 - CartManager (购物车管理器)
这是一个核心类,负责购物车的所有业务逻辑,如添加商品、移除商品、清空购物车、计算总价等。

CartManager.java
// CartManager.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CartManager {
// 使用 Map 来存储购物车项,key 是商品ID,方便快速查找和更新
private Map<Integer, CartItem> items;
public CartManager() {
this.items = new HashMap<>();
}
// 添加商品到购物车
public void addItem(Product product) {
CartItem item = items.get(product.getId());
if (item == null) {
// 如果购物车中没有该商品,则新增一项
items.put(product.getId(), new CartItem(product, 1));
} else {
// 如果已存在,则数量加一
item.setQuantity(item.getQuantity() + 1);
}
}
// 从购物车移除商品
public void removeItem(int productId) {
items.remove(productId);
}
// 更新购物车中商品的数量
public void updateQuantity(int productId, int newQuantity) {
CartItem item = items.get(productId);
if (item != null) {
if (newQuantity > 0) {
item.setQuantity(newQuantity);
} else {
// 如果数量小于等于0,则移除该商品
items.remove(productId);
}
}
}
// 清空购物车
public void clearCart() {
items.clear();
}
// 获取购物车中所有商品项
public List<CartItem> getItems() {
return new ArrayList<>(items.values());
}
// 计算购物车总金额
public double getTotalPrice() {
double total = 0.0;
for (CartItem item : items.values()) {
total += item.getSubtotal();
}
return total;
}
}
步骤 3:在 Servlet 中使用 HttpSession 管理购物车
我们创建 Servlet 来处理用户请求,如浏览商品、添加商品、查看购物车等。
假设我们有一个商品列表页面 (products.jsp),可以点击“加入购物车”链接。
AddToCartServlet.java 这个 Servlet 处理“加入购物车”的请求。
// AddToCartServlet.java
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/addToCart")
public class AddToCartServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 获取请求参数(商品ID)
String productIdStr = req.getParameter("id");
if (productIdStr == null || productIdStr.isEmpty()) {
resp.sendRedirect("products.jsp"); // 如果没有ID,重定向回商品页
return;
}
int productId = Integer.parseInt(productIdStr);
// 2. 模拟从数据库或服务层获取商品信息
// 在实际项目中,这里应该调用 DAO 或 Service 层
Product product = findProductById(productId); // 假设这个方法能找到商品
// 3. 获取或创建 HttpSession
HttpSession session = req.getSession(true); // session 不存在,则创建一个新的
// 4. 从 Session 中获取购物车对象
// 我们使用 "cart" 作为 Session 中存储购物车的键
CartManager cart = (CartManager) session.getAttribute("cart");
// 5. 如果购物车不存在,则创建一个新的并放入 Session
if (cart == null) {
cart = new CartManager();
session.setAttribute("cart", cart);
}
// 6. 将商品添加到购物车
if (product != null) {
cart.addItem(product);
}
// 7. 重定向到购物车页面或商品列表页面
resp.sendRedirect("cart.jsp");
}
// 模拟方法:根据ID查找商品
private Product findProductById(int id) {
// 这里只是模拟,实际项目中应该从数据库查询
switch (id) {
case 1: return new Product(1, "Java编程思想", 108.00);
case 2: return new Product(2, "深入理解Java虚拟机", 89.00);
case 3: return new Product(3, "Effective Java", 75.00);
default: return null;
}
}
}
ViewCartServlet.java 这个 Servlet 用于显示购物车内容。
// ViewCartServlet.java
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@WebServlet("/viewCart")
public class ViewCartServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession(false); // 不自动创建session
CartManager cart = null;
if (session != null) {
cart = (CartManager) session.getAttribute("cart");
}
// 如果购物车不存在或为空,设置一个空列表,避免JSP报错
if (cart == null || cart.getItems().isEmpty()) {
req.setAttribute("cartItems", new ArrayList<CartItem>());
req.setAttribute("totalPrice", 0.0);
} else {
req.setAttribute("cartItems", cart.getItems());
req.setAttribute("totalPrice", cart.getTotalPrice());
}
// 转发到 JSP 页面进行展示
req.getRequestDispatcher("cart.jsp").forward(req, resp);
}
}
步骤 4:创建 JSP 页面进行展示和交互
products.jsp (商品列表页)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>商品列表</title>
</head>
<body>
<h1>欢迎光临我们的书店</h1>
<ul>
<li>
<a href="addToCart?id=1">Java编程思想 - ¥108.00</a>
</li>
<li>
<a href="addToCart?id=2">深入理解Java虚拟机 - ¥89.00</a>
</li>
<li>
<a href="addToCart?id=3">Effective Java - ¥75.00</a>
</li>
</ul>
<hr>
<a href="viewCart">查看我的购物车</a>
</body>
</html>
cart.jsp (购物车页面)
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="com.example.CartItem" %>
<html>
<head>我的购物车</title>
</head>
<body>
<h1>我的购物车</h1>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>商品名称</th>
<th>单价</th>
<th>数量</th>
<th>小计</th>
</tr>
<%
List<CartItem> cartItems = (List<CartItem>) request.getAttribute("cartItems");
if (cartItems == null || cartItems.isEmpty()) {
%>
<tr>
<td colspan="4">您的购物车是空的!</td>
</tr>
<%
} else {
for (CartItem item : cartItems) {
%>
<tr>
<td><%= item.getProduct().getName() %></td>
<td>¥<%= String.format("%.2f", item.getProduct().getPrice()) %></td>
<td><%= item.getQuantity() %></td>
<td>¥<%= String.format("%.2f", item.getSubtotal()) %></td>
</tr>
<%
}
}
%>
</table>
<hr>
<p style="font-size: 1.2em; font-weight: bold;">
购物车总金额: ¥<%= String.format("%.2f", request.getAttribute("totalPrice")) %>
</p>
<a href="products.jsp">继续购物</a>
</body>
</html>
- 用户访问商品列表:用户打开
products.jsp,看到商品列表。 - 点击“加入购物车”:用户点击一个商品旁的链接,
addToCart?id=1。 AddToCartServlet处理请求:- 获取商品ID
1。 - 获取或创建
HttpSession对象。 - 从 Session 中以
"cart"为键取出CartManager对象,如果第一次购物,Session 中没有"cart",则创建一个新的CartManager实例并存入 Session。 - 调用
cart.addItem(product)将商品ID为1的商品加入购物车。 - 重定向到
cart.jsp。
- 获取商品ID
- 显示购物车:浏览器请求
cart.jsp。ViewCartServlet(或由 JSP 容器直接处理) 获取 Session 中的CartManager,计算总价,并将数据存入request作用域,然后转发给cart.jsp。 cart.jsp渲染页面:JSP 通过 EL 表达式或 JSP 脚本读取request中的数据,将购物车内容以表格形式展示给用户。
Session 购物车的优缺点
优点
- 实现简单:
HttpSession是 Servlet API 内置的,使用起来非常方便。 - 数据安全:购物车数据存储在服务器端,客户端无法篡改,保证了数据的安全性。
- 高性能(读取):由于数据在内存中,读取和更新速度非常快。
缺点
- 服务器内存压力大:每个活跃用户的 Session 都会占用服务器内存,如果用户量巨大,会消耗大量服务器资源,成为性能瓶颈。
- 非持久化:如果服务器重启,所有 Session 数据都会丢失,用户的购物车也会被清空,这在生产环境中是不可接受的。
- 分布式环境问题:在负载均衡或集群环境下,如果用户的请求被分发到不同的服务器,而 Session 数据只存储在其中一台服务器上,就会出现“找不到购物车”的问题(需要配置 Session 共享,如使用 Redis 或数据库)。
改进方案:结合数据库或缓存
对于生产环境,通常会采用更健壮的方案:
- 使用 Redis 作为 Session 存储:将 Session 数据从服务器内存转移到 Redis 中,这解决了服务器重启数据丢失和分布式环境 Session 共享的问题,Redis 本身是内存数据库,读写速度依然很快。
- 购物车数据存入数据库:将购物车信息作为用户数据的一部分存入数据库,这种方式数据最可靠,但读写性能不如内存,通常可以结合缓存(如 Redis)来提升性能,即“先读缓存,缓存没有再读数据库”。
HttpSession 购物车是学习和理解 Web 会话管理的绝佳入门方式,在实际项目中,需要根据业务规模和架构选择更合适的存储方案。
