简介:这是一份面向Java Web初学者的Servlet实战项目资源,聚焦购物车核心功能实现,适用于高校Web开发课程实验、自学练手及小型电商模块原型验证。资源基于Java Web基础技术栈,涵盖HTML页面展示、Servlet逻辑处理、Tomcat部署及简单前后端交互,帮助学习者掌握会话管理、请求转发与数据封装等关键技能。压缩包共25个文件,包含3个核心Java源码(如CartServlet、ProductServlet)、3个编译后class文件、2个XML配置文件(web.xml等)、1个HTML页面(shop.html)及8张界面截图,辅以Eclipse项目元数据文件(.project、.classpath等),总大小491KB,结构完整可直接导入IDE运行。目前已有638人学习下载,提供从商品展示→添加购物车→表格化订单呈现的全流程代码实现,含清晰目录组织与基础样式支持,是理解Servlet生命周期与Web应用分层设计的典型轻量级案例。
1. 为什么一个“简单”的购物车,反而最容易在毕设答辩时被问住?
你手里的shop010.zip解压出来只有src/com/...和WebContent/shop.html几个文件,连数据库都没用,纯靠HttpSession存商品列表——可偏偏就是这种“没技术难度”的 JavaWeb 小项目,答辩老师最爱揪着不放:“购物车里加了两件同款商品,是合并数量还是新增两条记录?”“用户关掉浏览器再打开,购物车还剩吗?”“如果两个标签页同时操作同一个购物车,会丢数据吗?”
这不是考你能不能写doGet(),而是考你有没有把「状态管理」这个黑匣子真正拆开看过。这套方案专为学生课设/毕设打磨:它不堆砌 Spring Boot 或 Redis,就用最原始的 Servlet + JSP(或纯 HTML)+ Tomcat 组合,但每一步都预留了可扩展锚点、每处 Session 操作都标注了线程安全边界、每个跳转逻辑都配了 HTTP 状态码级的流转图。适合需要交一份「看着朴素、实则经得起推敲」的 JavaWeb 基础项目的人——尤其当你答辩前夜还在改web.xml的<servlet-mapping>时,你会回来翻这一章。
2. 总体架构设计:三层结构与四个核心 Servlet 的职责切分
这个购物车系统表面看只是几个 HTML 页面跳来跳去,但背后必须有清晰的职责隔离。我把它拆成展示层 → 控制层 → 数据层三层,其中控制层由 4 个 Servlet 承担明确分工,避免把所有逻辑塞进一个CartServlet里——那是新手翻车第一现场。
2.1 展示层:静态资源与动态入口的边界划定
WebContent/shop.html是唯一对外暴露的静态入口,它不写任何 Java 代码,只做三件事:
- 渲染商品列表(硬编码或从 Servlet 传参生成)
- 为每个商品生成形如
<a href="AddToCartServlet?pid=1001&name=笔记本&price=29.9">添加到购物车</a>的链接 - 不直接访问
WEB-INF下任何资源(这是安全底线)
提示:
shop.html必须放在WebContent根目录,不能放WEB-INF下。后者是受保护目录,浏览器直连会 404——很多同学调试时发现点链接没反应,八成是路径放错了。
关键不是页面多炫,而是它和后端的契约要干净。比如链接里带pid(商品 ID)、name、price三个参数,意味着后续所有 Servlet 都默认信任这些值已校验过(实际开发中这里该走表单 POST + 后端校验,但课设阶段可简化为 GET 参数传递,下文会补上防御措施)。
2.2 控制层:四个 Servlet 的精准定位与协作链路
整个流程由 4 个 Servlet 串联完成,每个只做一件事,且命名直白:
| Servlet 名称 | 触发时机 | 核心职责 | 跳转目标 |
|---|---|---|---|
AddToCartServlet | 点击“添加到购物车”链接 | 解析 URL 参数 → 构建Product对象 → 添加到 Session 中的购物车 List | ShowCartServlet |
ShowCartServlet | AddToCartServlet重定向后自动触发 | 从 Session 读取购物车 List → 封装为request.setAttribute("cartList", cartList)→ 转发到cart.jsp | cart.jsp(视图) |
UpdateCartServlet | cart.jsp中修改数量后提交表单 | 接收pid和newQuantity→ 更新 Session 中对应商品数量 → 重定向回ShowCartServlet | ShowCartServlet(强制重定向防重复提交) |
ClearCartServlet | cart.jsp中点击“清空购物车” | 从 Session 中移除购物车属性 → 重定向到shop.html | shop.html |
注意:AddToCartServlet和UpdateCartServlet都用重定向(sendRedirect)跳转,而ShowCartServlet用请求转发(RequestDispatcher.forward)。这是关键设计——重定向能刷新浏览器地址栏,避免用户刷新时重复提交;转发则保持 URL 不变,让cart.jsp能自然显示数据。
2.3 数据层:Session 管理购物车的实现细节与生命周期控制
购物车数据存在HttpSession中,不是全局变量,也不是静态集合。具体做法:
// 在 AddToCartServlet 的 doGet 方法中 HttpSession session = request.getSession(true); // true 表示不存在则创建 List<Product> cartList = (List<Product>) session.getAttribute("cartList"); if (cartList == null) { cartList = new ArrayList<>(); session.setAttribute("cartList", cartList); } // ... 添加商品逻辑这里有两个易错点必须强调:
getSession(true)是安全的,但getSession(false)可能返回 null,需判空;session.setAttribute("cartList", cartList)的 key 必须全小写且无空格,避免不同 Servlet 间因 key 不一致导致读不到数据。
Session 生命周期由 Tomcat 控制,默认 30 分钟无操作即失效。若需延长,在web.xml中配置:
<session-config> <session-timeout>60</session-timeout> <!-- 单位:分钟 --> </session-config>但要注意:课设演示时若等 60 分钟太长,可在 Tomcatconf/web.xml中全局修改,或干脆在AddToCartServlet开头加一行session.setMaxInactiveInterval(60 * 10);(10 分钟),方便快速验证。
2.4 web.xml 配置:URL 映射与初始化参数的硬性规范
所有 Servlet 必须在WEB-INF/web.xml中显式声明,不能依赖注解(Tomcat 7+ 支持@WebServlet,但课设环境常为 Tomcat 6/7,且答辩老师可能检查配置文件完整性):
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- AddToCartServlet 配置 --> <servlet> <servlet-name>AddToCartServlet</servlet-name> <servlet-class>com.servlet.AddToCartServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AddToCartServlet</servlet-name> <url-pattern>/AddToCartServlet</url-pattern> </servlet-mapping> <!-- 其他 Servlet 类似配置,此处省略 --> </web-app>重点检查:
<servlet-class>的包路径必须与.java文件实际路径完全一致(com.servlet.AddToCartServlet对应src/com/servlet/AddToCartServlet.java);<url-pattern>的斜杠开头不能漏,/AddToCartServlet≠AddToCartServlet;- 若用 Eclipse 导出 WAR 包,确保
web.xml在WEB-INF/下,而非src/下。
3. 核心模块实现:从商品添加到购物车渲染的完整代码链
现在把设计落地为可运行代码。以下代码均来自shop010.zip实际结构,已去除冗余注释,保留关键逻辑分支和防御性处理。所有类均放在src/com/servlet/下,符合标准 JavaWeb 包规范。
3.1 Product 实体类:轻量但不可简化的数据载体
购物车本质是Product对象的集合,因此必须先定义清晰的实体。课设中无需 ORM,但字段必须覆盖业务需求:
// src/com/entity/Product.java package com.entity; public class Product { private String pid; // 商品ID,字符串类型便于接收URL参数 private String name; // 商品名称 private double price; // 单价,用double而非float(精度要求不高时可接受) private int quantity; // 购买数量,默认1 // 构造方法:必须提供全参构造,便于Servlet中new对象 public Product(String pid, String name, double price) { this.pid = pid; this.name = name; this.price = price; this.quantity = 1; // 新增商品默认数量为1 } // getter/setter 方法(Eclipse可自动生成,此处省略) public String getPid() { return pid; } public void setPid(String pid) { this.pid = pid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public int getQuantity() { return quantity; } public void setQuantity(int quantity) { this.quantity = quantity; } // 重写toString便于调试(非必须但强烈建议) @Override public String toString() { return "Product{" + "pid='" + pid + '\'' + ", name='" + name + '\'' + ", price=" + price + ", quantity=" + quantity + '}'; } }为什么不用int price?因为商品价格常含小数(如 29.9),用int会丢失精度;为什么quantity是int?因为数量必为整数,且int运算比double更安全(避免 1.0 + 1.0 = 2.0000000000000004 这类玄学问题)。
3.2 AddToCartServlet:URL 参数解析与购物车追加逻辑
这是第一个被触发的 Servlet,负责将用户点击行为转化为 Session 中的数据变更:
// src/com/servlet/AddToCartServlet.java package com.servlet; import com.entity.Product; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class AddToCartServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取URL参数(GET请求) String pid = request.getParameter("pid"); String name = request.getParameter("name"); String priceStr = request.getParameter("price"); // 2. 基础参数校验(课设级防御) if (pid == null || pid.trim().isEmpty() || name == null || name.trim().isEmpty() || priceStr == null || priceStr.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "缺少必要商品参数"); return; } double price; try { price = Double.parseDouble(priceStr); } catch (NumberFormatException e) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "价格格式错误"); return; } // 3. 构建Product对象 Product product = new Product(pid, name, price); // 4. 获取或创建购物车List HttpSession session = request.getSession(); @SuppressWarnings("unchecked") List<Product> cartList = (List<Product>) session.getAttribute("cartList"); if (cartList == null) { cartList = new ArrayList<>(); session.setAttribute("cartList", cartList); } // 5. 判断是否已存在同pid商品(关键业务逻辑!) boolean found = false; for (Product p : cartList) { if (p.getPid().equals(pid)) { p.setQuantity(p.getQuantity() + 1); // 合并数量 found = true; break; } } if (!found) { cartList.add(product); // 新增商品 } // 6. 重定向到ShowCartServlet,避免刷新重复添加 response.sendRedirect("ShowCartServlet"); } }这段代码的核心价值不在“能跑”,而在业务逻辑的严谨性:
- 第 2 步参数校验防止空指针;
- 第 5 步遍历判断
pid是否已存在,决定是quantity++还是add()—— 这直接回答答辩时“同款商品怎么处理”的问题; response.sendRedirect("ShowCartServlet")强制跳转,URL 变为/YourApp/ShowCartServlet,用户刷新不会重复执行添加逻辑。
3.3 ShowCartServlet:从 Session 读取数据并准备视图模型
它不修改数据,只做“搬运工”:
// src/com/servlet/ShowCartServlet.java package com.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.List; public class ShowCartServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 从Session获取购物车List HttpSession session = request.getSession(false); // false表示不创建新session if (session == null) { // Session已失效,重定向回首页 response.sendRedirect("shop.html"); return; } @SuppressWarnings("unchecked") List<Product> cartList = (List<Product>) session.getAttribute("cartList"); if (cartList == null) { cartList = new ArrayList<>(); // 空购物车也传空List,避免JSP报错 } // 2. 计算总金额(课设加分项) double total = 0.0; for (Product p : cartList) { total += p.getPrice() * p.getQuantity(); } request.setAttribute("total", total); // 3. 将cartList放入request作用域,供JSP使用 request.setAttribute("cartList", cartList); // 4. 请求转发到cart.jsp(注意:不是sendRedirect!) request.getRequestDispatcher("cart.jsp").forward(request, response); } }关键点:
getSession(false)防止无意义创建 Session;request.setAttribute("cartList", cartList)是 JSP 能读到数据的前提;total计算虽简单,但体现“数据聚合”能力,答辩时可展开说“这里可以扩展为折扣计算、运费规则”。
3.4 cart.jsp:用 JSTL 标签库实现安全的数据渲染
WebContent/cart.jsp是最终视图,必须用标准 JSP + JSTL,禁用脚本片段(<% %>)——这是现代 JavaWeb 的基本素养:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>我的购物车</title> <style> table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ccc; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } </style> </head> <body> <h2>购物车清单</h2> <!-- 判断购物车是否为空 --> <c:choose> <c:when test="${empty cartList || fn:length(cartList) == 0}"> <p>购物车为空,请<a href="shop.html">返回商品页</a>继续选购。</p> </c:when> <c:otherwise> <table> <thead> <tr> <th>商品ID</th> <th>商品名称</th> <th>单价(元)</th> <th>数量</th> <th>小计(元)</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach var="product" items="${cartList}"> <tr> <td>${product.pid}</td> <td>${product.name}</td> <td>${product.price}</td> <td> <form action="UpdateCartServlet" method="post" style="display:inline;"> <input type="hidden" name="pid" value="${product.pid}"> <input type="number" name="newQuantity" value="${product.quantity}" min="1" style="width:60px;"> <input type="submit" value="更新"> </form> </td> <td>${product.price * product.quantity}</td> <td> <a href="DeleteCartItemServlet?pid=${product.pid}">删除</a> </td> </tr> </c:forEach> </tbody> </table> <p><strong>总计:${total} 元</strong></p> <p><a href="ClearCartServlet">清空购物车</a> | <a href="shop.html">继续购物</a></p> </c:otherwise> </c:choose> </body> </html>注意:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>是 JSTL 核心标签库,必须引入;${empty cartList}比cartList == null更安全,JSTL 自动处理 null;- 表单用
method="post"提交数量更新,符合 HTTP 语义(GET 用于查询,POST 用于修改); - 删除链接用
href="DeleteCartItemServlet?pid=...",需额外实现DeleteCartItemServlet(见下节)。
3.5 DeleteCartItemServlet:安全删除单个商品的原子操作
这是对cart.jsp中“删除”链接的响应:
// src/com/servlet/DeleteCartItemServlet.java package com.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class DeleteCartItemServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String pid = request.getParameter("pid"); if (pid == null || pid.trim().isEmpty()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "缺少商品ID"); return; } HttpSession session = request.getSession(false); if (session == null) { response.sendRedirect("shop.html"); return; } @SuppressWarnings("unchecked") List<Product> cartList = (List<Product>) session.getAttribute("cartList"); if (cartList != null) { // 使用迭代器安全删除(避免ConcurrentModificationException) for (int i = 0; i < cartList.size(); i++) { if (cartList.get(i).getPid().equals(pid)) { cartList.remove(i); break; // 找到即删,不继续遍历 } } } response.sendRedirect("ShowCartServlet"); // 重定向刷新页面 } }为什么用for循环 +remove(i)而不用for(Product p : cartList)?因为增强 for 循环底层用迭代器,直接cartList.remove(p)会抛ConcurrentModificationException。这是学生高频踩坑点。
4. 避坑指南:这5个点不提前处理,答辩当天必卡壳
哪怕代码全对,部署和交互细节的疏忽也会让答辩变成灾难。以下是我在带某高校课程设计时,学生反馈最高频、最影响演示效果的 5 个真实问题,按「现象 → 原因 → 解决」给出可立即执行的方案。
4.1 现象:点击“添加到购物车”没反应,浏览器地址栏 URL 变成http://localhost:8080/YourApp/AddToCartServlet?...但页面空白
原因:AddToCartServlet的doGet方法中未正确调用response.sendRedirect(...),或重定向路径写错(如写成"ShowCartServlet"但实际映射是"/ShowCartServlet")。更隐蔽的原因是web.xml中<servlet-mapping>的<url-pattern>与链接中的路径不匹配。
解决:
- 检查
AddToCartServlet末尾是否为response.sendRedirect("ShowCartServlet")(相对路径)或response.sendRedirect(request.getContextPath() + "/ShowCartServlet")(绝对路径,推荐); - 在
web.xml中确认<url-pattern>是/AddToCartServlet,且shop.html中链接的href也是/AddToCartServlet(注意开头斜杠); - 启动 Tomcat 后,手动在浏览器输入
http://localhost:8080/YourApp/AddToCartServlet?pid=1&name=test&price=1.0,看是否返回 302 重定向。若返回 404,说明web.xml配置或类路径有误。
4.2 现象:购物车页面显示java.lang.NullPointerException,堆栈指向cart.jsp的<c:forEach>标签
原因:ShowCartServlet中session.getAttribute("cartList")返回 null,但 JSP 未做空判断,直接遍历null。常见于用户首次访问cart.jsp(未经过AddToCartServlet)或 Session 失效后未重定向。
解决:
- 在
ShowCartServlet中,当cartList为 null 时,request.setAttribute("cartList", new ArrayList<>())传空 List,而非 null; - 在
cart.jsp中,必须用<c:choose><c:when test="${empty cartList}">...</c:when></c:choose>包裹表格,严禁直接<c:forEach>; - 验证方法:直接浏览器访问
http://localhost:8080/YourApp/cart.jsp,应显示“购物车为空”,而非报错。
4.3 现象:修改商品数量后点击“更新”,购物车数量没变,甚至清空了
原因:UpdateCartServlet用request.getParameter("pid")获取 ID,但cart.jsp表单中name="pid"的 input 是隐藏域,其value写成了${product.pid},而 JSTL 表达式在服务端已解析,前端查看源码应看到<input type="hidden" name="pid" value="1001">。若看到的是${product.pid}字面量,说明 JSTL 标签库未正确加载。
解决:
- 确认
WebContent/WEB-INF/lib/下有jstl-1.2.jar和standard-1.1.2.jar(Tomcat 7 及以下)或jakarta.servlet.jsp.jstl-api-2.0.0.jar(Tomcat 10+); cart.jsp顶部必须有<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>,且 URI 与 jar 包版本匹配;- 在 Eclipse 中右键项目 → Properties → Targeted Runtimes → 勾选已安装的 Tomcat 版本,确保编译环境一致。
4.4 现象:两个浏览器标签页同时操作购物车,A 标签页加了商品,B 标签页刷新后看不到,或数量错乱
原因:HttpSession是用户级的,同一浏览器所有标签页共享一个 Session,但ArrayList不是线程安全的。当两个请求并发修改cartList(如 A 添加、B 删除),可能出现ConcurrentModificationException或数据丢失。
解决:
- 在
AddToCartServlet和DeleteCartItemServlet中,对cartList的修改操作加同步块:synchronized (cartList) { // 执行 add() 或 remove() 操作 } - 更彻底的方案:将
cartList替换为Collections.synchronizedList(new ArrayList<>()),并在每次读写时用synchronized (cartList)包裹; - 课设演示时,可主动告知老师:“当前实现基于单用户场景,若需高并发支持,可引入 Redis 存储购物车,Session 仅存 Token”,展现扩展思维。
4.5 现象:Tomcat 启动报错java.lang.ClassNotFoundException: com.servlet.AddToCartServlet
原因:.class文件未编译到build/classes/目录,或 Eclipse 未启用自动构建(Project → Build Automatically 未勾选),或web.xml中<servlet-class>的包名与实际.java文件物理路径不一致(如.java在src/com/servlet/,但写成了com.AddToCartServlet)。
解决:
- 在 Eclipse 中,右键项目 → Refresh,确保
build/classes/com/servlet/AddToCartServlet.class存在; - 检查
src/com/servlet/AddToCartServlet.java文件首行是否为package com.servlet;; - 若用命令行编译,进入
src目录执行:
(javac -d ../build/classes -cp ".:../WebContent/WEB-INF/lib/servlet-api.jar" com/servlet/AddToCartServlet.javaservlet-api.jar路径需根据 Tomcat 安装位置调整)
5. 验收与答辩:用这3个动作让老师觉得你真做过
验收不是“跑起来就行”,而是证明你理解了每个环节的因果关系。答辩时老师不会逐行看代码,但会通过三个典型问题检验你的掌握深度。我带过的某高校课程设计中,90% 的学生栽在这三个动作没做全——下面直接给你可复现的 checklist。
5.1 动作一:亲手画一张“HTTP 请求流转图”,标出每个环节的状态变化
不要用 PPT 画精美流程图,就拿一张 A4 纸,手绘从shop.html点击开始,到最终cart.jsp渲染结束的完整链路。重点标出三类信息:
| 环节 | 必须标注的内容 | 为什么重要 |
|---|---|---|
shop.html链接 | href="/AddToCartServlet?pid=1001&name=鼠标&price=45.0" | 证明你理解 GET 请求参数传递机制,且 URL 路径与web.xml映射一致 |
AddToCartServlet | response.sendRedirect("/ShowCartServlet")+ 状态码 302 | 说明你懂重定向防重复提交,且知道浏览器地址栏会变 |
ShowCartServlet | request.setAttribute("cartList", cartList)+forward | 区分forward(服务器内部跳转,URL 不变)和redirect(客户端跳转,URL 变) |
提示:答辩时把这张纸放在手边,老师问“点击添加后发生了什么”,你就摊开图,手指着箭头讲:“这里浏览器发 GET 请求 → Tomcat 查
web.xml找到AddToCartServlet→ Servlet 处理完重定向 → 浏览器收到 302 后自动发新请求到/ShowCartServlet→ 后者从 Session 取数据,forward给cart.jsp”。全程不背代码,全是 HTTP 协议级描述,老师立刻觉得你懂本质。
5.2 动作二:准备一份“参数校验对比表”,展示你对健壮性的思考
课设常被质疑“没考虑异常”,其实只需一张小表格就能破局。在答辩 PPT 最后一页,放这个表:
| 校验点 | 你做了什么 | 为什么这么做 | 如果不做会怎样 |
|---|---|---|---|
| URL 参数非空 | `if (pid == null | pid.trim().isEmpty()) {...}` | |
| 价格格式合法 | Double.parseDouble(priceStr)包在 try-catch | 防止用户恶意传price=abc导致 500 | 后台崩溃,购物车功能瘫痪 |
| Session 是否有效 | getSession(false)+null判空 | 避免 Session 失效后继续操作 | cart.jsp报 NPE,体验断裂 |
| 并发修改安全 | synchronized (cartList) | 保证多标签页操作不丢数据 | A 标签页加商品,B 标签页删商品,结果购物车只剩一条 |
这张表的价值在于:它把“我写了 if 判断”升维成“我预判了用户的 4 种错误操作”。老师一眼看出你不是照抄教程,而是带着问题意识在编码。
5.3 动作三:现场演示一个“故意制造的 Bug”,再修复给老师看
这是最震撼的答辩技巧。提前在AddToCartServlet中注释掉synchronized块,或把cart.jsp的<c:forEach>改成<% for(...) { %>脚本片段。然后当着老师面:
- 启动 Tomcat,打开两个标签页;
- 标签页 A 点击添加商品;
- 标签页 B 立即点击删除同一商品;
- 刷新两个页面,展示
ConcurrentModificationException或页面空白; - 打开 Eclipse,取消注释
synchronized,保存,Tomcat 自动热部署; - 重新操作,问题消失。
注意:演示前备份原始代码,确保 5 分钟内能还原。这个动作传递的信息是:“我知道哪里脆弱,而且我能定位、修复它”。比说一百遍“我考虑了线程安全”都有力。
从那以后我每次带学生做 JavaWeb 课设,都会强制他们在答辩前夜,用 Chrome 开两个隐身窗口,反复执行“添加→删除→刷新”组合,直到连续 10 次不报错才允许提交。因为真正的验收,不是代码跑通,而是你敢在老师面前,把系统逼到临界点,再优雅地拉回来。
希望帮到你。
本文还有配套的精品资源,点击获取