简介:这份购物车案例【简单版】面向Java Web初学者与课程设计者,用JSP、Servlet、Tomcat与数据库搭建一个可运行的在线购物车,帮助理解Session状态保持、请求响应流程与前后端协作。压缩包共62个文件,约916KB,包含13个java源文件、26个class编译文件、6个jsp页面、5个xml配置、4个jar依赖,以及少量html、js与工程配置文件,覆盖实体、DAO、Service、Servlet分层与页面展示。已有670人学习下载。案例完整呈现商品列表、加入购物车、查看与删除商品等核心流程,购物车数据存放于服务端Session,可对照学习JSP展示、Servlet业务处理、JDBC持久化与Tomcat部署的整合方式,并附有安全性与性能优化的延伸提示,适合作为Java Web入门练手或课程设计参考。
1. 从一份 JSP 购物车源码说起:为什么 Session 才是这个案例的真正主角
很多人第一次接触 Web 开发时,都会被“购物车”这个经典案例卡住。页面能显示商品,点“加入购物车”也有反应,但一刷新数据就没了,或者换个浏览器登录同一账号,购物车内容对不上。这份《购物车案例【简单版】.rar》就是冲着这个问题来的——它用最朴素的 JSP + Java + Tomcat + HTML 组合,把“用户会话期间购物车数据如何保持”这件事讲透了。整个项目没有引入 Spring、MyBatis 这些框架,也没有前后端分离,所有逻辑都跑在服务端,靠 Session 把商品列表挂在用户会话上。适合刚学完 Servlet 和 JSP、想找一个能跑通、能改动的完整小项目练手的人。你拿到源码后,不需要配数据库,也不需要装额外中间件,Tomcat 一跑就能看到效果。
2. 项目结构拆解:从 web.xml 到 JSP 页面的完整调用链
2.1 目录布局与文件职责
拿到压缩包解压后,你会看到一个标准的 Java Web 项目目录。没有 Maven 或 Gradle 构建文件,说明它是按传统 Eclipse 动态 Web 项目结构组织的。常见做法是根目录下放src和WebContent(或webapp),src里是 Java 包,WebContent里是 JSP、HTML、CSS 和WEB-INF。
ShoppingCart/ ├── src/ │ └── com/ │ └── cart/ │ ├── model/ │ │ ├── Product.java // 商品实体 │ │ └── CartItem.java // 购物车条目 │ └── servlet/ │ ├── AddToCartServlet.java │ ├── ViewCartServlet.java │ └── RemoveFromCartServlet.java ├── WebContent/ │ ├── index.jsp // 商品列表页 │ ├── cart.jsp // 购物车展示页 │ ├── css/ │ │ └── style.css │ └── WEB-INF/ │ └── web.xml // Servlet 映射Product.java只包含商品 ID、名称、价格三个字段,CartItem则把Product和数量包在一起。Servlet 负责接收请求、操作 Session 中的购物车列表,JSP 负责渲染。web.xml里配置了三个 Servlet 的 URL 映射,比如/addToCart、/viewCart、/removeFromCart。如果你用 Tomcat 10 及以上版本,注意web.xml的 schema 版本要匹配,否则启动会报解析错误。
2.2 核心数据模型:Product 与 CartItem
先看两个实体类,它们决定了 Session 里存什么。代码很简短,但字段命名和类型选择直接影响后续 JSP 取值。
package com.cart.model; public class Product { private int id; private String name; private double price; public Product(int id, String name, double price) { this.id = id; this.name = name; this.price = price; } // getter 和 setter 省略 }package com.cart.model; public class CartItem { private Product product; private int quantity; public CartItem(Product product, int quantity) { this.product = product; this.quantity = quantity; } // getter 和 setter 省略 }Product的id用int而不是String,是为了在 Session 中做去重判断时方便比较。CartItem没有直接继承Product,而是用组合的方式持有Product对象,这样当商品信息需要扩展(比如加图片路径)时,购物车条目不用改结构。我一般会在CartItem里加一个getSubtotal()方法,返回product.getPrice() * quantity,这样 JSP 页面直接调用即可,不用在页面里写乘法表达式。
2.3 Session 中的购物车数据结构
购物车在 Session 里到底存成什么?常见做法是用List<CartItem>,而不是Map。因为商品数量通常不多,列表遍历足够快,而且 JSP 里用 JSTL 的c:forEach渲染更直观。
// 在 AddToCartServlet 的 doPost 中 HttpSession session = request.getSession(); List<CartItem> cart = (List<CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new ArrayList<>(); session.setAttribute("cart", cart); } int productId = Integer.parseInt(request.getParameter("id")); // 检查购物车中是否已有该商品 boolean found = false; for (CartItem item : cart) { if (item.getProduct().getId() == productId) { item.setQuantity(item.getQuantity() + 1); found = true; break; } } if (!found) { // 从商品库中查找商品,这里简化为硬编码 Product p = ProductRepository.findById(productId); cart.add(new CartItem(p, 1)); } response.sendRedirect("cart.jsp");这段代码的关键点有三个:第一,session.getAttribute("cart")返回Object,必须强转,且要处理null;第二,去重逻辑用for循环而不是contains,因为CartItem没有重写equals,直接contains比较的是对象地址;第三,添加完成后用sendRedirect而不是forward,避免用户刷新页面时重复提交。参数id来自商品列表页的链接,比如<a href="addToCart?id=1">加入购物车</a>。
2.4 web.xml 中的 Servlet 映射
虽然现在很多人用注解@WebServlet,但这份源码用的是web.xml配置。两种方式效果一样,但web.xml更直观地展示了请求路径和 Servlet 类的对应关系。
<servlet> <servlet-name>AddToCartServlet</servlet-name> <servlet-class>com.cart.servlet.AddToCartServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddToCartServlet</servlet-name> <url-pattern>/addToCart</url-pattern> </servlet-mapping>url-pattern决定了浏览器里访问的路径。如果你改成@WebServlet("/addToCart"),就可以删掉这段 XML。注意url-pattern不要和已有的 JSP 文件名冲突,比如/cart.jsp是物理文件,Servlet 映射成/cart才不会打架。
3. 本地跑起来:Tomcat 部署与 JSP 页面调试
3.1 环境准备与项目导入
跑这个项目需要 JDK 8 或 11(Tomcat 9 对 JDK 8 兼容最好),以及 Tomcat 9.x。不建议用 Tomcat 10,因为 10 以上版本把javax.servlet改成了jakarta.servlet,这份源码里的import javax.servlet.*会全部报错。如果你只有 Tomcat 10,要么改包名,要么换 Tomcat 9。
导入步骤:Eclipse 里选File -> Import -> Existing Projects into Workspace,选中解压后的文件夹。如果项目没有.project文件,就新建一个Dynamic Web Project,把src和WebContent的内容拷进去。然后在Project Facets里确认 Java 版本和 Dynamic Web Module 版本匹配 Tomcat 的 Servlet 版本。
3.2 部署到 Tomcat 并访问首页
在 Eclipse 里右键项目 ->Run As -> Run on Server,选择已配置的 Tomcat 9。启动后浏览器访问http://localhost:8080/ShoppingCart/index.jsp。如果 404,检查WebContent是否被识别为 Web 根目录,以及index.jsp是否在根目录下。
index.jsp里通常硬编码了几个商品,用 JSTL 或 Scriptlet 遍历输出。如果是 Scriptlet 写法,你会看到<% for(Product p : productList) { %>这样的代码。这种写法现在不推荐,但在这个简单案例里能跑通,也方便你理解请求响应周期。
3.3 加入购物车与查看购物车的完整流程
点击“加入购物车”后,浏览器请求/addToCart?id=1,AddToCartServlet处理完重定向到cart.jsp。cart.jsp从 Session 中取出cart属性,遍历显示。关键代码在cart.jsp顶部:
<% List<CartItem> cart = (List<CartItem>) session.getAttribute("cart"); if (cart == null || cart.isEmpty()) { %> <p>购物车是空的</p> <% } else { %> <table> <tr><th>商品</th><th>单价</th><th>数量</th><th>小计</th><th>操作</th></tr> <% for (CartItem item : cart) { %> <tr> <td><%= item.getProduct().getName() %></td> <td><%= item.getProduct().getPrice() %></td> <td><%= item.getQuantity() %></td> <td><%= item.getProduct().getPrice() * item.getQuantity() %></td> <td><a href="removeFromCart?id=<%= item.getProduct().getId() %>">删除</a></td> </tr> <% } %> </table> <% } %>这段 Scriptlet 混合了 Java 和 HTML,可读性一般,但胜在直接。注意session是 JSP 内置对象,不需要request.getSession()。如果页面显示乱码,在cart.jsp第一行加<%@ page contentType="text/html;charset=UTF-8" %>,并且在 Servlet 里设置response.setContentType("text/html;charset=UTF-8")。
3.4 删除商品与 Session 失效处理
RemoveFromCartServlet的逻辑是从 Session 的cart列表中移除指定id的CartItem。常见做法是用迭代器遍历,避免ConcurrentModificationException。
List<CartItem> cart = (List<CartItem>) session.getAttribute("cart"); if (cart != null) { Iterator<CartItem> it = cart.iterator(); while (it.hasNext()) { CartItem item = it.next(); if (item.getProduct().getId() == productId) { it.remove(); break; } } } response.sendRedirect("cart.jsp");如果用户直接关闭浏览器再打开,Session 默认在 30 分钟后失效,购物车会清空。你可以在web.xml里调整<session-config><session-timeout>60</session-timeout></session-config>,单位是分钟。注意这个超时是滑动过期,每次请求都会重置计时。
4. 避坑与排查:JSP 购物车最容易翻车的五个地方
4.1 中文乱码:从请求参数到页面输出
现象:商品名称显示成???或乱码。原因:Tomcat 默认用 ISO-8859-1 解析 GET 请求参数,POST 请求也取决于request.setCharacterEncoding是否调用。解决:在 Servlet 的doPost第一行加request.setCharacterEncoding("UTF-8");对于 GET 请求,在 Tomcat 的server.xml里给 Connector 加URIEncoding="UTF-8"。JSP 页面用<%@ page pageEncoding="UTF-8" %>。
4.2 刷新导致重复添加
现象:在cart.jsp按 F5 刷新,购物车数量又加了一次。原因:添加逻辑用forward转发到了 JSP,浏览器地址栏还是/addToCart,刷新就重复提交。解决:添加完成后用response.sendRedirect("cart.jsp"),让浏览器发起新请求。这是 PRG(Post-Redirect-Get)模式,能根治重复提交。
4.3 Session 中的购物车为 null
现象:访问cart.jsp报NullPointerException。原因:用户直接访问cart.jsp,从未调用过addToCart,Session 里没有cart属性。解决:在cart.jsp里先判断cart == null,或者写一个CartFilter在请求到达 JSP 前初始化空列表。我一般会在ViewCartServlet里统一初始化,然后让cart.jsp只负责渲染。
4.4 Tomcat 版本不兼容导致 404
现象:所有 Servlet 请求都 404,但 JSP 能访问。原因:web.xml的servlet-class写错包名,或者 Tomcat 10 的jakarta.servlet与javax.servlet不匹配。解决:检查WEB-INF/classes下是否有编译后的.class文件;如果是 Tomcat 10,把import javax.servlet.*全部改成import jakarta.servlet.*,并更新web.xml的 schema。
4.5 商品 ID 类型转换异常
现象:点击“加入购物车”报NumberFormatException。原因:request.getParameter("id")返回null或非数字字符串。解决:加一层判空和try-catch,或者用Integer.parseInt前先if (idStr != null && idStr.matches("\\d+"))。常见做法是在 JSP 生成链接时确保id是数字,比如<a href="addToCart?id=<%= p.getId() %>">。
5. 二次开发:从硬编码商品到可扩展的购物车
5.1 把商品数据抽到配置文件或数据库
当前商品列表硬编码在ProductRepository里,改商品要重新编译。最省事的改造是把商品数据放到WEB-INF/products.properties,用Properties类加载。再进一步就是接 MySQL,加一个ProductDao,用 JDBC 查询。注意引入数据库后,Session 里存的Product对象最好只存 ID 和数量,渲染时再查库,避免 Session 数据过期。
// 用 Properties 加载商品 Properties props = new Properties(); props.load(getClass().getClassLoader().getResourceAsStream("products.properties")); // 格式:1=苹果,5.005.2 用 JSTL 和 EL 表达式替换 Scriptlet
Scriptlet 在 JSP 里写 Java 代码,维护起来很痛苦。改成 JSTL 后,cart.jsp会清爽很多:
<c:forEach var="item" items="${sessionScope.cart}"> <tr> <td>${item.product.name}</td> <td>${item.product.price}</td> <td>${item.quantity}</td> <td>${item.product.price * item.quantity}</td> <td><a href="removeFromCart?id=${item.product.id}">删除</a></td> </tr> </c:forEach>需要引入jstl.jar和standard.jar,并在页面顶部加<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>。EL 表达式里的${sessionScope.cart}等价于session.getAttribute("cart"),但不用强转,也不会抛NullPointerException。
5.3 增加库存校验与数量修改
现在的“加入购物车”没有库存概念,数量也只能加不能减。改造思路:在CartItem里加stock字段,添加时判断quantity < stock;在cart.jsp里把数量列改成输入框,提交到UpdateCartServlet更新 Session 中的数量。注意更新数量时也要用 PRG 模式,避免刷新重复提交。
| 改造点 | 原实现 | 建议方案 |
|---|---|---|
| 商品数据 | 硬编码 | Properties 或 MySQL |
| 页面渲染 | Scriptlet | JSTL + EL |
| 数量修改 | 不支持 | 输入框 + UpdateServlet |
| 库存校验 | 无 | CartItem 加 stock 字段 |
| 会话超时 | 默认 30 分钟 | web.xml 配置 60 分钟 |
5.4 一个我踩过的坑:Session 共享与多标签页
同一个浏览器开两个标签页,都访问index.jsp,一个加商品 A,另一个加商品 B,最后购物车里两个都有——这是正常的,因为 Session 是同一个。但如果你在标签页 1 删除了商品 A,标签页 2 的页面不会自动刷新,还显示着旧数据。这不是 Bug,是 HTTP 无状态导致的。解决办法是每次操作后都重定向到cart.jsp,让页面重新从 Session 取数据。从那以后我每次做 Session 相关的功能,都强制走一遍“操作-重定向-渲染”的流程,不偷懒用forward。希望帮到你。
本文还有配套的精品资源,点击获取