JSP购物车案例:Session会话管理与Tomcat部署入门 简介这份购物车案例【简单版】压缩包面向Java Web入门学习者与课程实训场景围绕JSP、Servlet、Tomcat、HTML及Session等基础技术构建一个可运行的简易在线购物车帮助读者理解商品展示、加入购物车、删除条目与会话状态保持等完整流程。包内共62个文件以26个class、13个java源文件、6个jsp页面和5个xml配置为主另含jar依赖、js脚本与html页面等压缩包约916KB体量轻巧但层次齐全。目前已有671人学习适合作为课堂示例或课后练手素材。代码按entity、dao、service、servlet等模块组织配合商品列表页、购物车页和Web配置文件可用于梳理浏览器请求到后端处理的响应链路、Session中购物车数据的更新方式以及数据库访问的基本写法也为后续补充输入校验、安全防护与性能优化留下清晰的改造起点。对于刚接触Java Web的读者能据此对照页面、控制器与数据层的协作关系快速建立项目整体认识。1. 先弄明白一件事购物车在后端到底「记」了什么第一次听到「购物车案例【简单版】」这个名字多数人会愣一下——购物车不是网站上那个小图标吗怎么还成了一个可下载的文件换个场景就通了。你在超市推的那辆车作用只有一个在你走到收银台之前帮你把想买的东西先攒着。网站上的购物车干的是同一件事只不过推车的人换成了浏览器管车的人换成了服务器。这个案例带你把最小的一圈走完用 HTML 摆出货架用 JSP 处理「加一件」这个动作用 Java 的集合把清单存在服务器的 Session 里再交给 Tomcat 把结果送回浏览器。没有数据库没有框架三四个文件就能跑起来。它适合完全没碰过服务端、只写过静态页面的人当作第一个真正「会动」的网页练习。2. 把工具箱摊开JDK、Tomcat 和那个不能再简单的目录结构2.1 先看一眼要装的三样东西在动手写代码之前得先有一个能跑 Java 网站的「地基」。这个购物车案例只依赖三样东西JDK、Tomcat、一个放代码的文件夹。听起来有点多其实把它们的关系理清就很简单——JDK 是让 Java 代码能被运行的发动机Tomcat 是接待浏览器请求的门面文件夹则是你写页面和逻辑的地方。JDK 建议选 8 或 11 这两个长期维护版本装完之后要确认环境变量 JAVA_HOME 指向安装目录PATH 里带上 bin。检查是否成功打开命令行敲一句# 两个命令都返回版本号说明 JDK 装好了 java -version javac -versionjava -version看的是运行时javac -version看的是编译器。这两个都要有输出只成功一个多半是环境变量没配对。JSP 在第一次被访问的时候需要被翻译成 Java 再编译成 class缺了编译器页面就会给你甩一个 500 错误。Tomcat 建议用 8.5 或 9 的压缩包版本解压到一个路径里没有中文、没有空格的目录比如D:\server\tomcat9。为什么不装安装版因为压缩包解压即用出了问题整个删掉重来对新手最省事。进来之后你会看到这么几个目录记住两个就够了tomcat9/ ├── bin/ # startup.bat 启动shutdown.bat 关闭 ├── conf/ # server.xml 在这里改端口用的 ├── logs/ # 启动失败的时候答案基本都在这 ├── work/ # JSP 被翻译后的 Java 和 class 文件放在这 └── webapps/ # 你的网站项目就扔在这里bin是开关webapps是货架。Windows 双击bin/startup.batmacOS 或 Linux 用bin/startup.sh。启动成功的标志是命令行窗口里刷出一段包含启动耗时和端口号的日志最后停住不动——那个窗口不能关关了服务就停了。提示如果窗口闪一下就消失了先别怀疑代码八成是 JAVA_HOME 没配对。去logs目录里翻最新的日志文件第一屏的报错通常就写着找不到 Java 运行时。2.2 项目目录长什么样在 webapps 下新建一个 cart 文件夹Tomcat 有一个很贴心的约定webapps下每一个文件夹都会被自动当成一个独立的网站。文件夹名叫cart你在浏览器里访问的地址就是http://localhost:8080/cart/。所以部署这件事在这里就是「建文件夹、丢文件」。先建好这样的结构webapps/ └── cart/ ├── index.jsp # 商品列表页 ├── addToCart.jsp # 处理加入购物车 ├── cart.jsp # 查看购物车 ├── updateCart.jsp # 改数量、清空 └── WEB-INF/ # 建着暂时空着WEB-INF这个文件夹现在不用往里放东西但建议先建出来。它是 Tomcat 的一个保护目录浏览器无法直接访问里面的文件只有服务器内部可以转发过去。以后你写配置、放类文件都会用到它先留着位置。启动 Tomcat 之后先别急着访问自己的页面用浏览器打开http://localhost:8080/看到 Tomcat 的默认首页说明服务本身是通的。这一步很多人跳过结果后面页面打不开分不清是 Tomcat 没起来还是自己的代码写错了。先把地基验一遍能省掉后面一大半的排查时间。接着访问http://localhost:8080/cart/index.jsp。这时候文件还不存在页面会返回 404。这个 404 是正常的它在告诉你Tomcat 已经认出cart这个项目了只是里面还没有页面。到这一步工具链就算备齐了。2.3 JSP 到底是什么HTML 里夹几段 Java很多人对 JSP 的困惑在于它看着像 HTML又能写 Java那它到底是啥把它理解成「一张会自己算数的 HTML 模板」就好。你照常写标签、写表格、写按钮遇到需要数据的地方用% %插一段 Java 进去。Tomcat 收到第一次请求时会把这个.jsp文件翻译成一个.java源文件再编译成.class然后执行、把执行结果拼成纯 HTML 返回给浏览器。翻译出来的东西就放在work目录里。浏览器最终拿到的永远是普通的 HTML看不到一行 Java 代码。这个机制解释了两件新手常遇到的事第一JSP 第一次访问会慢一点因为要编译第二次就快了第二你改了 JSP 但页面没变很可能是work里的旧编译结果还在这一点后面第 5 章会专门说。至于 Session先记住一句话就够用它是服务器端的一块存储空间每个浏览器对应一份浏览器靠一个叫 JSESSIONID 的 Cookie 来认领自己的那一份。购物车之所以能「记住」你放了什么靠的就是把清单存进这块空间。具体怎么存、怎么取第 4 章会一行一行拆开。3. 第一个能跑的例子点一下「加入购物车」到底发生了什么3.1 商品列表页把三件商品写死在 index.jsp 里先从最简单的开始。真实项目里商品数据来自数据库这个案例为了让你专注在「加车」这个动作上直接写死在页面里。三个商品一个表格每行一个表单。在webapps/cart/下新建index.jsp% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 % !DOCTYPE html html head meta charsetUTF-8 title商品列表/title /head body h2商品列表/h2 table border1 cellpadding8 tr th编号/thth名称/thth单价/thth操作/th /tr tr td1001/tdtd机械键盘/tdtd199/td td !-- 用表单 POST 提交避免中文商品名出现在地址栏里变成乱码 -- form actionaddToCart.jsp methodpost input typehidden nameid value1001 input typehidden namename value机械键盘 input typehidden nameprice value199 input typesubmit value加入购物车 /form /td /tr tr td1002/tdtd无线鼠标/tdtd89/td td form actionaddToCart.jsp methodpost input typehidden nameid value1002 input typehidden namename value无线鼠标 input typehidden nameprice value89 input typesubmit value加入购物车 /form /td /tr tr td1003/tdtd显示器支架/tdtd129/td td form actionaddToCart.jsp methodpost input typehidden nameid value1003 input typehidden namename value显示器支架 input typehidden nameprice value129 input typesubmit value加入购物车 /form /td /tr /table pa hrefcart.jsp查看购物车/a/p /body /html最上面那行page指令是这套代码的「身份证」三个属性各管一件事contentType告诉浏览器用什么编码解析这段 HTMLpageEncoding告诉 Tomcat 这个文件本身是用什么编码保存的language说明里面嵌的是 Java。三个都写 UTF-8中文才不会变成问号。input typehidden是隐藏域用户在页面上看不见但提交时会跟着表单一起送过去。商品编号、名称、单价就是靠这三个隐藏域传给了下一个页面。为什么不直接把参数拼在链接里因为中文放进 URL 需要额外编码新手在这里翻车概率极高用 POST 表单能绕开这个问题。页面里没有一行 Java 代码它就是一个普通 HTML。这说明 JSP 的能力是「可选的」你能用 Java也可以不用。现在刷新http://localhost:8080/cart/index.jsp应该能看到三行商品和一个按钮。如果连这个页面都打不开先回到第 2 章检查地址里的项目名有没有写错。3.2 加入购物车的动作addToCart.jsp 里的核心几行点按钮的那一刻浏览器把表单数据 POST 给了addToCart.jsp。这个页面不显示任何东西它只做一件事把商品塞进购物车然后把你送回列表页。新建addToCart.jsp% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 % % page importjava.util.* % % // 1. 设置请求编码否则 POST 过来的中文商品名会变成乱码 request.setCharacterEncoding(UTF-8); // 2. 从表单里取出三个参数名字要和 index.jsp 里的 name 属性一致 String id request.getParameter(id); String name request.getParameter(name); String price request.getParameter(price); // 3. 从 session 里拿购物车第一次访问时是 null需要新建一个 MapString, String[] cart (MapString, String[]) session.getAttribute(cart); if (cart null) { cart new HashMapString, String[](); session.setAttribute(cart, cart); } // 4. 已经在车里就把数量加一没有就放一条新记录 String[] item cart.get(id); if (item null) { // 数组三个位置分别存名称、单价、数量 cart.put(id, new String[]{name, price, 1}); } else { int count Integer.parseInt(item[2]) 1; cart.put(id, new String[]{item[0], item[1], String.valueOf(count)}); } // 5. 跳回列表页避免用户按 F5 时重复提交 response.sendRedirect(index.jsp); %一段一段看。第 1 步的setCharacterEncoding必须在读取任何参数之前调用顺序错了就不起作用这是新手最容易忽略的细节。第 2 步的getParameter(id)括号里的字符串要和表单里input nameid完全一致大小写敏感。名字对不上取出来就是 null后面会直接引发空指针。第 3 步是关键。session.getAttribute(cart)拿的是上一件商品加车时存进去的那个 Map。第一次来的人当然拿不到所以判断 null 之后新建一个空 Map再用setAttribute放回去。这个「先取、没有就建、再存回去」的三步是 Session 编程的固定套路几乎每个功能都是这个形状。第 4 步用商品编号做主键。Map 的 key 是编号value 是一个长度 3 的字符串数组{名称, 单价, 数量}。同一个商品第二次加车时取出旧数组、把数量加一、再放回去。数组是不可变的所以是整体替换不是原地修改。第 5 步用sendRedirect跳回列表页。这里不用转发forward因为跳转后浏览器地址栏变成index.jsp用户刷新时重新请求的是列表页不会把刚才的加车动作再执行一遍。这个小细节决定了你的购物车会不会莫名其妙多出好几件。3.3 完整跑一遍从点按钮到购物车里多一行现在把两个页面串起来走一遍。浏览器打开http://localhost:8080/cart/index.jsp在「机械键盘」那一行点「加入购物车」。你会看到页面闪了一下回到商品列表。地址栏是http://localhost:8080/cart/index.jsp不是addToCart.jsp这说明重定向生效了。再点两次同一个按钮理论上购物车里应该有 3 件机械键盘。但这时候还没有查看页面所以先去浏览器开发者工具里验证一下按 F12找到 Application 或 Storage 面板看 Cookie 那一栏应该有一条JSESSIONID。这条 Cookie 就是你的「寄存牌」服务器靠它在内存里找到属于你的那个 Map。想更快确认数据对不对可以临时写一个极简的调试页面debug.jsp% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 % % page importjava.util.* % % // 只用来偷看 session 里存了什么验证完记得删掉 MapString, String[] cart (MapString, String[]) session.getAttribute(cart); if (cart ! null) { for (Map.EntryString, String[] e : cart.entrySet()) { out.println(e.getKey() - Arrays.toString(e.getValue()) br); } } else { out.println(cart 还是 null); } %打开它应该看到类似1001 - [机械键盘, 199, 3]的输出。看到这一行说明整条链路已经通了HTML 表单把数据送到 JSPJSP 把数据放进 Session 的 Map 里Map 跟着你的浏览器走。注意这类调试页面验证完就删掉或者挪进 WEB-INF别留在能直接访问的位置。它会把服务端内存里的东西原样打印出来属于「自己看没问题给别人看是事故」的那类代码。到这一步你已经跑通了整个案例最核心的一个动作。剩下的页面本质上是在这个 Map 上做加减法。4. 让购物车跟着你走Session 里到底存了什么4.1 Session 是什么服务器发的「寄存牌」把 Session 想成超市的寄存柜。你进门的时候柜子管理员给你一张牌牌上有个编号。之后你每次来存东西、取东西只要出示这张牌管理员就知道该打开哪个柜子。你不需要记住柜子在几号位置牌会替你记。放进代码里看session.setAttribute(cart, cart)是往柜子里放东西session.getAttribute(cart)是凭牌取东西那块牌就是浏览器里的JSESSIONIDCookie。整个过程你一行代码都没写过「怎么发牌」Tomcat 自动做了——第一次请求时它生成一个随机字符串通过响应头里的Set-Cookie发给浏览器浏览器之后每次请求都自动带上。这解释了新手最常问的两个问题。「为什么我换个浏览器购物车就空了」因为牌是发到那个浏览器里的换一个浏览器等于换了个人自然拿到一个新柜子。「为什么关掉浏览器再打开就没了」因为这张牌默认是会话级 Cookie浏览器关掉就丢。Tomcat 那边还有另一个超时机制默认 30 分钟没人访问服务端也会把柜子清空。两个机制叠加就出现了「放了一晚上第二天全没了」。提示这两个默认值都能改。超时时间在WEB-INF/web.xml里加一段session-configsession-timeout60/session-timeout/session-config单位是分钟。别指望靠这个做「永久购物车」真要长期保存得写进数据库那是下一步的事。还有一个新手容易忽略的点Session 存在服务器内存里。你重启一次 Tomcat所有柜子全部清空。所以在开发阶段频繁重启的时候购物车动不动就没了不一定是代码写错很可能只是服务重启了。分清这两种「消失」排查方向完全不同。4.2 把 Map 读出来cart.jsp 怎么把清单渲染成表格存进去之后要能看见。新建cart.jsp把 Session 里的 Map 遍历出来变成表格% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 % % page importjava.util.* % % MapString, String[] cart (MapString, String[]) session.getAttribute(cart); double total 0; // 用来累加总价 % !DOCTYPE html html headmeta charsetUTF-8title购物车/title/head body h2我的购物车/h2 % if (cart null || cart.isEmpty()) { % p购物车还是空的a hrefindex.jsp去逛逛/a/p % } else { % table border1 cellpadding8 tr th编号/thth名称/thth单价/thth数量/thth小计/th /tr % for (Map.EntryString, String[] entry : cart.entrySet()) { String[] v entry.getValue(); int count Integer.parseInt(v[2]); double price Double.parseDouble(v[1]); total count * price; % tr td% entry.getKey() %/td td% v[0] %/td td% v[1] %/td td a hrefupdateCart.jsp?id% entry.getKey() %actionminus-/a % count % a hrefupdateCart.jsp?id% entry.getKey() %actionplus/a /td td% count * price %/td /tr % } % /table p合计% total % 元/p pa hrefupdateCart.jsp?actionclear清空购物车/a/p % } % pa hrefindex.jsp继续购物/a/p /body /html几个点值得停一下。第一行取cart之后立刻判断 null这个顺序不能反。如果先cart.isEmpty()再判 null第一次访问时就会空指针。total声明在脚本片段里但在下面的循环中不断累加最后在循环外面输出。JSP 的% %片段之间共享变量因为它们最终会被拼进同一个_jspService方法里。这一点理解了你就明白为什么变量名在整个页面内不能重复声明两次。entry.getKey()和v[0]看起来有点绕其实就是 Map 的键和值。键是商品编号 1001值是那个三格数组。数字用Double.parseDouble转因为单价存的是字符串。数量那一列的加减链接把商品编号和动作作为参数拼在 URL 里传给updateCart.jsp。这里用 GET 是因为它只是「改一个状态」没有敏感数据而且方便刷新和回退。中文商品名没有放进 URL所以不存在编码问题。4.3 改数量、删一行、清空三个动作共用一个页面加减和清空本质都是对那个 Map 做操作没必要写三个页面。新建updateCart.jsp一个文件搞定% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 % % page importjava.util.* % % MapString, String[] cart (MapString, String[]) session.getAttribute(cart); String action request.getParameter(action); // plus / minus / clear String id request.getParameter(id); if (cart ! null clear.equals(action)) { // 清空直接清掉 Map但 session 本身还在 cart.clear(); } else if (cart ! null id ! null) { String[] item cart.get(id); if (item ! null) { int count Integer.parseInt(item[2]); count plus.equals(action) ? count 1 : count - 1; if (count 0) { // 减到 0 就把这条记录整个删掉别留一行数量为 0 的空壳 cart.remove(id); } else { item[2] String.valueOf(count); } } } // 处理完统一跳回购物车页面 response.sendRedirect(cart.jsp); %action参数决定走哪条分支。clear走清空其他情况带着 id 走加减。减到 0 时用remove而不是把数量存成 0这个选择很重要。如果留着数量为 0 的记录购物车页面就会显示一行「显示器支架 × 0小计 0」看着像个 bug。新手做这类功能边界条件比主流程更容易出错。注意cart.clear()和session.removeAttribute(cart)的区别。前者清空 Map 里的内容Map 对象还在后者把整个 Map 从 Session 里摘掉下次再访问getAttribute又变成 null得重新走「新建」的逻辑。两种写法都能达到「清空」的效果但前者的判断分支更少不容易出岔子。最后一句sendRedirect(cart.jsp)是必须的。如果不跳转用户改完数量按 F5浏览器会问「是否重新提交表单」——而这里用的是链接GET刷新会重复执行一次加减数量会诡异地多一或少一。统一跳转回展示页这个坑就堵住了。到这里案例的四个页面全部跑通。你手上有一个能加、能改、能删、能算总价、还能跨页面记住状态的购物车了。5. 新手翻车现场五个最常见的报错和它的病根5.1 编码和缓存这一类乱码、改完看不到变化坑一中文全是问号或者方块现象商品列表正常但加进购物车之后名称显示成????或者一堆菱形问号。原因编码在三处可能不一致——JSP 文件本身用什么编码保存、pageEncoding声明的是什么、request.setCharacterEncoding设的是什么。任何一处对不上中文就会在半路被截断。还有一种情况是文件被编辑器存成了 GBK但页面声明写的是 UTF-8。解决三步一起对齐。文件用 UTF-8 保存编辑器右下角能看到当前编码pageEncodingUTF-8和contentTypetext/html;charsetUTF-8都写上处理参数的 JSP 第一行加request.setCharacterEncoding(UTF-8)。如果参数是通过 URL 传的GETsetCharacterEncoding不管用得在 Tomcat 的conf/server.xml里给 Connector 加上URIEncodingUTF-8。这就是为什么第 3 章用表单 POST 而不是拼链接——能少踩一个坑。坑二改完 JSP 刷新页面还是老样子现象明明把「合计」改成了「总金额」刷新浏览器还是显示旧文案甚至删掉的代码还在跑。原因Tomcat 会把 JSP 编译成 class 放在work目录里正常情况下你改动源文件它会重新编译但有时候时间戳判断失灵或者你改的是被复制到别处的另一份文件。解决先确认你改的是webapps/cart/下的那份不是桌面上的备份。仍然不生效就把work目录下对应项目的缓存删掉让它重新编译。最省事的做法是停掉 Tomcat、删work里那个文件夹、再启动。我自己的习惯是改完配置类文件后直接重启一次虽然慢十几秒但省掉「对着屏幕怀疑人生」的十分钟。5.2 提交和会话这一类重复加车、购物车突然空了坑三刷新页面购物车数量莫名多了一件现象点了一次「加入购物车」跳回列表后按了一下 F5回去看购物车发现多了两件。原因浏览器刷新时会重新发送上一次的请求。如果上一次请求是 POST 表单提交那这个「加车」动作就被执行了第二遍。这是 HTTP 的机制不是 bug。解决加车处理完之后必须重定向也就是第 3 章最后那句response.sendRedirect。这个模式有个名字叫「提交后重定向」是表单处理的标配写法。判断标准很简单凡是会修改数据的操作处理完都跳走别把用户留在处理页面上。坑四购物车过一会儿就空了或者换个浏览器就没了现象刚加完还在去泡了杯茶回来刷新空了。或者在自己电脑上好好的发给同学一看是空的。原因Session 有两个「寿命」——浏览器 Cookie 的寿命和服务端的超时。默认情况下关掉浏览器 Cookie 就没了服务端 30 分钟没人访问也会回收。换浏览器、换设备等于换了一个人当然看不到你的购物车。解决先接受这个设定它是 Session 的正常行为不是代码写错。开发阶段频繁重启 Tomcat 也会导致清空这种「消失」和超时无关。真要做长期保存的购物车得把数据写进数据库用用户 ID 关联这是第 6 章要走向的地方。当下只需确认一件事同一台电脑、同一个浏览器、不重启服务的情况下购物车是稳定的那就说明你的 Session 代码没问题。5.3 起不来这一类端口被占用和路径写错坑五启动窗口闪退或者提示端口被占用现象双击startup.bat窗口一闪就没了或者日志里出现Address already in use浏览器打不开任何页面。原因8080这个端口被别的程序占了最常见的是你之前启动的 Tomcat 还在后台跑着又启动了一次。也可能是别的开发工具默认用了这个端口。解决先确认是不是自己的旧进程没关用shutdown.bat停一下再启动。确实被别的程序占了就改端口——打开conf/server.xml找到Connector port8080那一行把 8080 改成 8081重启后访问地址相应变成http://localhost:8081/cart/。!-- conf/server.xml 里找到这一段只改 port 的值 -- Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /还有一类「打不开」跟端口无关是地址写错了。访问http://localhost:8080/cart/index.jsp时项目名cart必须和webapps下的文件夹名一字不差大小写也要一致。有人把文件夹建成Cart访问时写小写得到 404 就开始怀疑 Tomcat。遇到 404先念一遍地址协议对不对、端口对不对、项目名对不对、文件名对不对四段逐个核对通常三秒内就能找到问题。6. 跑通之后往哪走从 JSP 到 Servlet 的升级路线跑通之后你大概会有个感觉能跑但页面里% %到处都是看着乱。这不是你的错觉把 Java 代码和 HTML 混在一个文件里稍微复杂一点就维护不动。下一步的方向就是把这件事拆开。6.1 第一件事把 Java 代码从 JSP 里搬出去现在的addToCart.jsp里没有一行 HTML它本质上就是个处理逻辑的「控制器」只是恰好用了 JSP 的壳子。把它改写成 Servlet逻辑一字不改只是换了个写法// 继承 HttpServlet只处理 POST 请求 public class AddToCartServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String id request.getParameter(id); String name request.getParameter(name); String price request.getParameter(price); // 注意这里从 request 拿 session不是从 JSP 的内置对象 HttpSession session request.getSession(); MapString, String[] cart (MapString, String[]) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } String[] item cart.get(id); if (item null) { cart.put(id, new String[]{name, price, 1}); } else { item[2] String.valueOf(Integer.parseInt(item[2]) 1); } response.sendRedirect(index.jsp); } }对比一下就能看出差别JSP 里的session是内置对象Servlet 里要自己request.getSession()拿JSP 里的response也是内置的Servlet 里是方法参数。除此之外Map 的存取逻辑完全一样。所以你在这个案例里学到的东西不是白学的换到哪里都是这套。Servlet 需要编译class 文件要放到WEB-INF/classes下还要在web.xml里配servlet和servlet-mapping或者用注解WebServlet(/addToCart)。这一步会逼着你第一次接触「编译」「类路径」这些概念刚开始会有点烦但跨过去之后你才真正开始写 Java Web 而不是写模板。6.2 再往前走的三条线阶段现在的问题该补的东西第一步代码和页面混在一起Servlet 做控制、JSP 只做展示也就是常说的 MVC第二步重启就丢数据没有用户概念JDBC 建表存商品和购物车写增删改查第三步页面里全是% %EL 表达式和 JSTL 标签用${cart.total}替代脚本片段第四步谁都能访问没法区分用户Cookie 与 Session 的区别、Filter 拦截、登录状态给个实在的优先级先把 Servlet 版跑通再把数据写进一张数据库表。这两步做完你已经能做出一个「像那么回事」的小系统了。EL 和 JSTL 可以边做边学它们只是让页面更好看、更干净不改变背后的逻辑。别一上来就扑向框架Map 和 Session 都没搞明白的时候去学框架只会把不懂的东西再包一层。学习路径上还有个小提醒每学一个新东西就回头改一遍这个购物车。学 Servlet 就把它改成 Servlet 版学 JDBC 就把 Map 换成数据库学 EL 就把% %换成${}。同一个需求反复重写比做十个不同的练习题更能看出差别在哪。我第一次跑这类案例的时候改完addToCart.jsp顺手刷新浏览器发现数量没变以为是 Session 坏了折腾了快半小时才发现是work里的旧编译结果没更新。从那以后我每次改完代码、尤其是改完配置和 Java 类都强制停一次服务、清一遍缓存、重启、再刷新这套动作慢是慢但再没被「改了没反应」这种事骗过。希望你少走这一段弯路也希望帮到你。本文还有配套的精品资源点击获取