简介:这份购物车案例【简单版】压缩包面向Java Web初学者与课程设计者,提供一套基于JSP、Servlet、Tomcat与数据库的入门级电商购物车实现方案,帮助理解动态网页与服务器端状态管理的完整链路。包内共62个文件,以26个class与13个java源码为主体,配合6个jsp页面、5个xml配置、4个jar依赖及少量html、js与工程配置文件,整体约916KB,结构涵盖entity、dao、service、servlet等分层模块,便于对照阅读。目前已有670人学习下载。案例完整演示了商品列表展示、加入购物车、查看与删除购物车内容等流程,购物车数据存放于Session中,可直观体会JSP负责展示、Servlet处理请求、数据库持久化商品信息、Tomcat提供HTTP服务的协同方式,并涉及JDBC连接与Session过期等基础要点,适合作为Java Web入门练手或课程作业参考。
1. 从一份 JSP 购物车压缩包说起:为什么 session 才是核心
很多刚接触 Java Web 的朋友,第一个像样的练手项目就是购物车。你拿到一份名为「购物车案例【简单版】.rar」的压缩包,解压后看到一堆.jsp、.java和web.xml,心里大概会犯嘀咕:这东西到底能不能跑起来,跑起来之后又能学到什么。我拆过不少这类教学包,说实话,大部分「简单版」购物车真正有价值的地方,不是页面长什么样,而是它把session这个黑匣子用最直白的方式摊开给你看了。
这个资源本质上是一个基于 JSP + Java + Tomcat 的传统 Web 应用,没有用任何现代框架,连 Servlet 都可能是最原始的写法。它的适用人群很明确:正在学 Java Web 基础、需要理解 HTTP 无状态特性和会话跟踪机制的人。你如果已经用惯了 Spring Boot,这份代码看起来会有点「考古」,但恰恰是这种原始感,能让你看清购物车数据到底存在哪、怎么存、什么时候丢。接下来我会按「先跑通、再拆解、后避坑」的顺序,把这份压缩包里的东西讲透。
2. 把压缩包跑起来:Tomcat 部署与 JSP 页面流转
2.1 环境准备与目录结构确认
拿到压缩包后别急着双击,先解压到一个纯英文路径下。我见过太多因为路径里有中文或空格导致 Tomcat 启动报FileNotFoundException的情况,这种翻车完全没必要。解压后你会看到类似这样的结构:
shopping-cart-simple/ ├── src/ # Java 源码目录 │ └── com/example/cart/ │ ├── model/ # 实体类,比如 Product、CartItem │ └── servlet/ # 控制层,处理加入购物车、查看购物车 ├── web/ # Web 资源目录 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 依赖 jar 包,通常只有 servlet-api │ ├── index.jsp # 商品列表页 │ ├── cart.jsp # 购物车展示页 │ └── css/ # 样式文件 └── README.txt # 可能存在的说明文件如果你的压缩包里没有src和web的区分,而是直接一堆 JSP 文件,那说明它更偏向纯 JSP 模型,业务逻辑可能直接写在<% %>脚本片段里。两种结构都能跑,但后者维护起来更头疼。确认结构后,打开你的 IDE(Eclipse 或 IntelliJ IDEA 都行),把项目导入为 Dynamic Web Project 或 Maven 项目。如果原包没有pom.xml,我一般会手动补一个最简的 Maven 配置,方便管理依赖和构建。
2.2 Tomcat 配置与启动验证
Tomcat 版本选择上,这份老代码建议用 Tomcat 8.5 或 9.0。Tomcat 10 开始把javax.servlet改成了jakarta.servlet,老代码直接扔进去会报ClassNotFoundException,这是最常见的「跑不起来」原因之一。配置步骤不复杂:
# 假设 Tomcat 安装在 /opt/tomcat9 # 把项目编译后的 web 目录或 war 包放到 webapps 下 cp -r shopping-cart-simple/web /opt/tomcat9/webapps/cart # 启动 Tomcat /opt/tomcat9/bin/startup.sh # 查看日志确认无异常 tail -f /opt/tomcat9/logs/catalina.out启动后访问http://localhost:8080/cart/index.jsp,如果能看到商品列表,说明部署成功。这里有个细节:很多教学包的web.xml里<welcome-file-list>配置的是index.jsp,但如果你直接访问http://localhost:8080/cart/却报 404,检查一下web.xml的版本声明是不是3.0以上,低版本对默认欢迎页的支持有差异。
2.3 从 index.jsp 到 cart.jsp 的数据流转
页面流转的逻辑是理解这个案例的关键。index.jsp通常遍历一个商品列表(可能是硬编码在页面里的,也可能是从数据库查的),每个商品旁边有个「加入购物车」的链接或表单。点击后请求发到某个 Servlet,Servlet 从request里取出商品 ID,然后从session里拿到已有的购物车对象,把商品塞进去,最后重定向到cart.jsp。
// 典型的加入购物车 Servlet 核心逻辑 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取商品 ID String productId = request.getParameter("id"); // 2. 从 session 中获取购物车,没有就新建 HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } // 3. 更新数量 cart.put(productId, cart.getOrDefault(productId, 0) + 1); // 4. 重定向到购物车页面 response.sendRedirect("cart.jsp"); }这段代码里request.getSession()是核心。它先尝试获取当前请求关联的 session,如果没有就创建一个新的。session.setAttribute("cart", cart)把购物车对象绑定到会话上,这样同一个浏览器后续的请求都能拿到同一个 cart。sendRedirect而不是forward,是为了避免用户刷新页面时重复提交。理解了这个流转,你就明白为什么关掉浏览器再打开,购物车就空了——因为 session 默认是会话级别的,浏览器关闭后 JSESSIONID 这个 Cookie 就失效了。
3. session 与购物车数据结构:拆开看每个类的职责
3.1 购物车到底该用什么数据结构存
这是我在带新人时被问得最多的问题之一。这份简单版案例里,购物车通常用Map<String, Integer>或者List<CartItem>来存。两种方式各有适用场景,选错了后面改起来很痛苦。
用Map的写法,key 是商品 ID,value 是数量。优点是判断商品是否已在购物车、更新数量非常快,O(1) 时间复杂度。缺点是没法存商品的其他信息,比如单价、名称,展示的时候还得根据 ID 去查商品表。用List<CartItem>的写法,每个CartItem是一个对象,包含商品完整信息和数量。优点是展示方便,直接遍历就行。缺点是每次添加都要遍历列表判断是否已存在,数据量大时性能差。
我一般会建议:如果商品数据是从数据库来的,用Map存 ID 和数量,展示时批量查库;如果商品数据是静态的或者缓存在内存里,直接用List<CartItem>更省事。这份简单版案例大概率用的是Map,因为代码量最少。
// CartItem 实体类的典型定义 public class CartItem { private String productId; private String productName; private double price; private int quantity; // 计算小计金额 public double getSubtotal() { return price * quantity; } // getter 和 setter 省略 }注意getSubtotal()这个方法,很多新手会把它写成静态工具方法,其实放在实体类里更合理,因为小计只跟当前条目有关。这种设计上的小细节,在简单版里往往被忽略,但你自己重构的时候可以留意。
3.2 session 的创建、销毁与超时配置
session不是凭空存在的,它依赖 Cookie 或 URL 重写来维持。Tomcat 默认会在响应头里加Set-Cookie: JSESSIONID=xxx,浏览器后续请求自动带上。如果你在浏览器里禁用了 Cookie,就得用response.encodeURL()把 session ID 拼到 URL 后面,否则每次请求都会新建 session,购物车永远加不进去。
超时时间配置在web.xml里:
<session-config> <!-- 单位是分钟,这里设置 30 分钟 --> <session-timeout>30</session-timeout> </session-config>30 分钟意味着用户 30 分钟没操作,session 就被 Tomcat 回收了。对于购物车场景,这个时间其实偏短。我见过有电商项目把超时设成 2 小时甚至更长,但那样会占用服务器内存。折中方案是用 Redis 做 session 共享,不过那就超出这份简单版的范畴了。你在这个案例里要验证的是:改小超时时间(比如 1 分钟),然后静置观察购物车是否清空,以此确认配置生效。
3.3 商品列表的渲染与 JSP 脚本片段
index.jsp里渲染商品列表的代码,通常是这样的:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.util.*" %> <html> <head><title>商品列表</title></head> <body> <h2>商品列表</h2> <table border="1"> <tr><th>ID</th><th>名称</th><th>价格</th><th>操作</th></tr> <% // 模拟商品数据,实际项目应从数据库或 Servlet 传入 List<Map<String, Object>> products = new ArrayList<>(); Map<String, Object> p1 = new HashMap<>(); p1.put("id", "1001"); p1.put("name", "机械键盘"); p1.put("price", 399.00); products.add(p1); for (Map<String, Object> p : products) { %> <tr> <td><%= p.get("id") %></td> <td><%= p.get("name") %></td> <td><%= p.get("price") %></td> <td><a href="addToCart?id=<%= p.get("id") %>">加入购物车</a></td> </tr> <% } %> </table> </body> </html>这种在 JSP 里直接写 Java 代码的方式,现在看确实不优雅,但它的好处是直观——你能一眼看到数据从哪来、到哪去。<%= %>是输出表达式,<% %>是脚本片段。注意addToCart这个链接,它对应web.xml里配置的 Servlet 映射,或者用@WebServlet("/addToCart")注解。如果你改了 Servlet 的 URL 模式,这里也要同步改,否则点击后 404。
4. 避坑与排查:为什么你的购物车加不进去
4.1 点击加入购物车后页面 404
现象:在index.jsp点击「加入购物车」,浏览器跳转后显示 404 Not Found。 原因:Servlet 的 URL 映射和 JSP 里写的链接不一致。常见情况是web.xml里配了/addToCart,但 JSP 里写的是addToCart(少了斜杠),或者 Servlet 类上的@WebServlet注解值拼写错误。 解决:打开web.xml或 Servlet 类,确认<url-pattern>或注解值与 JSP 里的href完全一致。注意大小写敏感,addToCart和addtocart是两个不同的路径。
4.2 购物车页面显示为空,但明明加过了
现象:点击加入购物车后跳转到cart.jsp,页面显示「购物车为空」。 原因:cart.jsp里获取 session 属性的 key 和 Servlet 里设置的 key 不一致。比如 Servlet 里写的是session.setAttribute("cart", cart),但 JSP 里写的是session.getAttribute("shoppingCart")。 解决:全局搜索setAttribute和getAttribute,确保 key 字符串完全一样。建议把 key 定义成常量,比如public static final String CART_KEY = "cart";,两边都引用这个常量。
4.3 刷新页面导致重复添加商品
现象:在cart.jsp按 F5 刷新,购物车里的商品数量又增加了一次。 原因:加入购物车的 Servlet 用的是forward而不是sendRedirect,或者表单提交后没有做重定向。浏览器刷新时重新提交了上一次的 POST 请求。 解决:在 Servlet 处理完添加逻辑后,使用response.sendRedirect("cart.jsp")重定向。重定向会让浏览器发起一个新的 GET 请求,刷新时就不会重复提交了。这是 PRG(Post-Redirect-Get)模式的基本应用。
4.4 Tomcat 10 启动报 ClassNotFoundException
现象:项目在 Tomcat 9 能跑,换到 Tomcat 10 后启动报java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet。 原因:Tomcat 10 把 Servlet API 的包名从javax.servlet迁移到了jakarta.servlet,老代码里的 import 语句全部失效。 解决:要么换回 Tomcat 9,要么把所有javax.servlet替换成jakarta.servlet,并更新web.xml的 schema 声明。对于这份简单版案例,我建议直接用 Tomcat 9,省去迁移的麻烦。
4.5 中文商品名显示乱码
现象:商品名称是中文,在页面上显示成??????或乱码。 原因:JSP 页面的pageEncoding和响应头的charset不一致,或者 Servlet 里没有设置response.setContentType("text/html;charset=UTF-8")。 解决:在 JSP 顶部确保<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>,在 Servlet 的doGet或doPost第一行加上request.setCharacterEncoding("UTF-8")和response.setContentType("text/html;charset=UTF-8")。如果是 GET 请求传中文,Tomcat 8 以后默认 URI 编码是 UTF-8,一般不用额外处理,但 POST 请求必须手动设。
5. 进阶技巧:把 session 购物车改造成可持久化的版本
这份简单版最大的局限是 session 存在内存里,Tomcat 一重启购物车就没了,而且多台服务器部署时 session 不共享。如果你想让这个案例更接近真实项目,可以做一个最小改造:把购物车数据在每次变更后写入 Cookie 或本地文件,启动时再读回来。这里我演示用 Cookie 做持久化的思路,因为不依赖额外服务。
// 在加入购物车后,把购物车序列化写入 Cookie protected void saveCartToCookie(HttpServletResponse response, Map<String, Integer> cart) throws UnsupportedEncodingException { // 把 Map 转成 "id1:qty1,id2:qty2" 的字符串格式 StringBuilder sb = new StringBuilder(); for (Map.Entry<String, Integer> entry : cart.entrySet()) { sb.append(entry.getKey()).append(":").append(entry.getValue()).append(","); } // URL 编码防止中文或特殊字符问题 String cartStr = URLEncoder.encode(sb.toString(), "UTF-8"); Cookie cookie = new Cookie("cart", cartStr); cookie.setMaxAge(7 * 24 * 60 * 60); // 保存 7 天 cookie.setPath("/"); response.addCookie(cookie); }这段代码的关键参数是setMaxAge,单位是秒。设成 7 天意味着即使用户关闭浏览器,购物车数据还在。setPath("/")保证整个应用都能读到这个 Cookie。读取的时候反向解析:从request.getCookies()里找到名为cart的 Cookie,URL 解码后按逗号拆分,再按冒号拆出 ID 和数量。
但 Cookie 方案有硬限制:单个 Cookie 最大 4KB,商品多了就存不下。而且用户清空浏览器 Cookie 后数据照样丢。所以更靠谱的做法是存数据库——用户登录后,把 session 里的购物车合并到数据库表中,下次登录时再加载。这个改造涉及用户体系,已经超出简单版的范围,但你可以先把这个思路记下来,等学完 JDBC 或 MyBatis 再回来补全。
验证改造是否成功的方法很简单:加入几件商品,关掉浏览器再打开,访问cart.jsp看数据是否还在。如果还在,说明 Cookie 持久化生效了。如果不在,检查setMaxAge是否设了正值(负值表示会话 Cookie,关闭浏览器就失效),以及 Cookie 的 path 是否覆盖了当前应用路径。
从那以后我每次拿到这类教学压缩包,都会先跑通主流程,再挑一个核心机制(比如 session)做最小化改造,因为只有改过一遍,才算真正理解它。希望帮到你。
本文还有配套的精品资源,点击获取