ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java MVC选课系统:解压运行、三层架构与部署避坑全指南

Java MVC选课系统:解压运行、三层架构与部署避坑全指南 简介myweb.rar是一份基于Java MVC架构的学生选课系统完整可运行源码包面向Java Web初学者、课程设计及毕业设计学生。系统采用JSP作为视图层Servlet与JavaBean分别承担控制器和模型覆盖学生登录、课程浏览、选课退选、后台课程与用户管理等完整功能解压配置后即可在Tomcat服务器上运行。压缩包共82个文件大小约320KB以29个JSP页面、19个Java源文件与19个class文件为主体另含XML配置、dat数据文件、图片资源及Eclipse工程文件等目录按src、WebContent、classes划分代码结构清晰便于定位。已有190人学习浏览过该资源。通过这份可运行实例可直观理解MVC三层如何实现关注点分离掌握JSPServletJavaBean的经典开发模式并学习登录校验、随机验证码、选课数据交互等Web开发常用技术适合后续在此基础上扩展功能或用于毕业设计二次开发。1. 打开这份 Java MVC 选课系统 RAR 前先弄清它到底是什么货色看到myweb.rar这种命名和一堆 JSP 文件列表很多人的第一反应是「2026 年了还有人交这种作业」。但如果你把 Spring Boot 全家桶放一边认真把这个压缩包拆开看会发现它其实是教科书意义上最标准的一版 Java MVC 选课系统——前端 JSP中间 Servlet后端 JavaBean Service连.classpath、.settings和org.eclipse.jst.common.project.facet.core.prefs都还在说明这是一份完整可导入 Eclipse 的 Web 工程不是网上常见的缺胳膊少腿的残包。它适合三类人正在做 Java Web 课程设计的学生、准备面试前想快速回顾 MVC 本质的开发者、以及想找一个能跑通的参照系来对比 Spring MVC 和原生 Servlet 差别的从业者。这篇文章我会直接从解压开始把运行配置、业务链路、前后台权限、验证码实现和常见部署坑全部过一遍。2. 拆解目录结构与三层架构映射先搞懂每个文件归谁管2.1 把 RAR 里的每个文件放进 MVC 的三个抽屉压缩包里文件不少但关系非常整齐。它没有用 Spring、没有 MyBatis就是纯粹的 Servlet JSP JavaBean 三层手写实现这一点我建议你先想清楚——正因为没有框架自动装配每个文件的存在感都非常强反而适合学习。按 MVC 的职责来划分WebContent 下的所有文件都在 View 层login.jsp/register.jsp/index.jsp/userindex.jsp登录、注册、入口、学生主页browseuserinfo.jsp/browsecourseinfo.jsp/browseselectedcourse.jsp学生浏览用户信息、浏览课程、查看已选课程adminindex.jsp/admin_browseuserinfo.jsp/admin_browsecourseinfo.jsp管理员后台首页、浏览用户、浏览课程admin_updateuserinfo.jsp/admin_updatecourseinfo.jsp/admin_deleteuserinfo.jsp/admin_deletecourseinfo.jsp后台的更新和删除页面selectcourse.jsp/selectcoursefail.jsp/updatecoursesuccess.jsp/updatefail.jsp/deleteselectedcourse.jsp/deleteselectedcoursefail.jsp选课及各种操作回显页面这些 JSP 的名字已经暴露了完整业务学生端能做浏览课程、选课、看已选课程、退选管理员端能做用户和课程的新增、更新、删除、浏览。没有引入任何前端框架页面之间靠redirect和forward串联状态靠 Session 和 request 传递——这正是原生 MVC 最直白的表达方式。src 目录下分了四个包这就是 Model 和 Controller 的归属地servlets所有控制器接收 JSP 页面的请求调用 service 层再决定跳转javabeans实体类对应数据库里的表结构services业务逻辑层util数据库连接、字符编码、随机码之类的工具类一个合格从业者看这个分包方式就能意识到它把 Controllerservlets、Serviceservices、Modeljavabeans util彻底拆开了。没有框架的情况下能自觉做这种隔离说明这份代码有清晰的分层意识。2.2 数据库连接工具看 util 包里的 DBHelper选课系统最核心的底层是数据库连接。util 包里的数据库工具类通常长这样package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class DBHelper { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/course_db?useUnicodetruecharacterEncodingutf-8; private static final String USERNAME root; private static final String PASSWORD root; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }逻辑上没太多可讲的就是经典的 JDBC 三步加载驱动、获取连接、提供关闭方法。但我想提醒你两件事。第一URL 里带了useUnicodetruecharacterEncodingutf-8这说明作者已经被中文乱码坑过。JSP 页面本身是动态渲染的如果数据库连接不带 UTF-8 参数request.getParameter()拿到的中文大概率是乱码。第二密码和 URL 是硬编码的我一般接手这类项目第一件事就是把它改成读取配置文件但初学阶段这样做反而更直观——你改错参数时直接在控制台就能看到是哪一行抛的异常。如果你用的是 MySQL 8.x记得把驱动类改成com.mysql.cj.jdbc.Driver否则加载驱动就会直接爆炸。2.3 Eclipse 工程导入与 Dynamic Web Module 版本核对这个工程自带.project、.classpath和.settings所以导入方式很简单Eclipse 菜单File - Import - Existing Projects into Workspace选myweb文件夹即可。但这里有一个每年都有人踩的坑——org.eclipse.wst.common.project.facet.core.xml里写的 Dynamic Web Module 版本。常见的报错是「Cannot change version of project facet Dynamic Web Module to 3.1」或者导入后 Tomcat 无法识别项目上下文。原因基本是.settings里写死的版本和你本机 Tomcat 支持的 Servlet 版本对不上。比如工程声明了 3.1对应 Tomcat 8.5但你配的是 Tomcat 9.x或者反过来。解决方式也很典型——右键项目Properties - Project Facets手动把 Dynamic Web Module 改成和你 Tomcat 匹配的版本然后 Maven 依赖调整先放一边直接 Clean 后重新部署。还有一点WebContent目录名是 Eclipse 默认的 Web 根目录概念但 Tomcat 部署后访问路径是http://localhost:8080/myweb/项目名myweb来自.project里的配置。如果你改了工程名部署后的上下文路径也会变这点在后续对接前端页面路径时特别注意——JSP 里如果写死了/myweb/xxx这种绝对路径改工程名后所有链接全部 404。3. 跑通学生端主链路注册、登录、验证码与选课完整流程3.1 注册和登录背后的 Servlet 跳转逻辑打开register.jsp和registerfail.jsp对照看注册页提交给哪个 Servlet在 web.xml 里能看到 mapping。典型的映射是这样servlet servlet-nameRegisterServlet/servlet-name servlet-classservlets.RegisterServlet/servlet-class /servlet servlet-mapping servlet-nameRegisterServlet/servlet-name url-pattern/register/url-pattern /servlet-mapping我想让你注意这行url-pattern/register/url-pattern。这意味着 JSP 表单的action指向的是/register而不是RegisterServlet的类名。Tomcat 会根据 web.xml 里的映射关系把这个路径转发给实际的 Servlet 处理。这是原生 Servlet 开发里最容易被新手忽略的点——改 Servlet 类名不需要动 JSP但要同步更新 web.xml。LoginServlet的逻辑一般是从 request 里取 username 和 password调用UserService.login()查库返回一个User对象然后把这个对象放进session.setAttribute(user, user)。如果查出来是null就response.sendRedirect(loginfail.jsp)成功则跳userindex.jsp。记住原生 Servlet 里跳转失败页用 redirect 而不是 forward是有意的——因为 forward 后 URL 不会变用户刷新页面会重复提交表单。3.2 随机验证码的实现怎么读说到random.jsp和randomImage.jsp这两个文件单独挑出来讲。因为它们生成验证码的方式是整个系统里最像「正规项目」的地方。// 伪代码示意实际以包内源码为准 BufferedImage image new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g image.getGraphics(); Random random new Random(); // 产生四位随机数 String code String.valueOf(random.nextInt(9000) 1000); request.getSession().setAttribute(randomCode, code); // 画干扰线 g.setColor(new Color(200, 200, 200)); for (int i 0; i 10; i) { int x1 random.nextInt(width); int y1 random.nextInt(height); g.drawLine(x1, y1, x1 random.nextInt(50), y1 random.nextInt(50)); } ImageIO.write(image, JPEG, response.getOutputStream());这是典型的 Java 图形绘制验证码方案左边random.jsp在页面上用img srcrandomImage.jsp方式引入图片生成时把随机字符串塞进 Session 的randomCode属性。登录时校验流程是取出request.getParameter(code)和 Session 里存的randomCode做 equals 比较。这里藏着一个非常微妙的坑——equals比较的顺序。String的equals是内容比较但如果用户输入的 code 是 nullcode.equals(randomCode)会抛NullPointerException。见过很多课程设计最后倒在这一步实际开发里应该用randomCode.equals(code)或者code ! null code.equals(randomCode)这样写。另外验证码用完最好立刻session.removeAttribute(randomCode)防止同一验证码重复使用这在真实系统中算一个小安全漏洞但课程设计里基本没人处理。3.3 选课与退选事务边界在哪选课核心逻辑在SelectCourseServlet处理流程大致是三步拿到课程 ID 和学生 ID先查是否已选过避免重复选课再判断课程容量是否已满最后插入选课记录。这中间最关键的是一致性保护问题。我在分析这份代码时特别注意到了一个点它把「检查是否已选」和「插入记录」放在同一个 Service 方法里但有没有加事务直接影响容错能力。如果CourseService.selectCourse()是先select count(*)再insert两个操作中间没有事务管理那在高并发场景下多个学生同时选最后一门课就可能超选。课程设计确实不太会测并发但你要知道边界在哪。退选逻辑对应DeleteselectedcourseServlet通常是删除选课表里对应的一条记录然后更新课程的已选人数。注意 JSP 文件名是deleteselectedcourse.jsp和deleteselectedcoursefail.jsp——失败页普遍存在说明作者在写每个操作时都留了失败回显的出口而不是只做成功路径这一点比很多只贴成功 CRUD 的示例代码要完善。学生端完整的可用流程是打开index.jsp→ 登录 → 进入userindex.jsp→ 点浏览课程 →browsecourseinfo.jsp→ 点选课 →selectcourse.jsp确认 → 成功进updatesuccess.jsp失败进selectcoursefail.jsp。走通这个闭环你就能理解原生 MVC 里 Controller 是怎么一层层把页面串起来的。4. 管理员端的 CRUD 与权限边界那些容易忽略的校验细节4.1 管理员页面的入口伪装与角色判断打开adminindex.jsp看它和学生端的userindex.jsp结构相似但内容全部是后台管理入口。这里有一个判断标准你可以用来评价这份代码的水平——它有没有校验「当前登录用户是管理员」好的做法是抽一个checkAdmin的方法在每个admin_*开头的 Servlet 里先判断session.getAttribute(user)是否为空、角色是否为admin不满足就直接response.sendRedirect(login.jsp)。如果这份代码只在 JSP 页面上显示「管理员专属菜单」而没有在 Servlet 里做拦截那它的权限设计就只是靠 UI 藏按钮——绕过前端直接请求 URL后台操作照样能执行。我看到文件名里有admin_browseuserinfo.jsp、admin_deleteuserinfo.jsp、admin_updateuserinfo.jsp这三个是完整的管理员用户管理链路。它们对应的 Servlet 应该分别处理查询列表、删除用户、更新用户信息。删除用户这个操作在真实系统里更合理的做法是软删除status 字段置 0但很多课设项目都是直接delete from users where id?。能跑但你要知道真实生产环境一般不硬删数据否则后面数据统计和审计都会出问题。4.2 课程管理的前台页面与 Servlet 参数传递admin_browsecourseinfo.jsp是管理员浏览所有课程的页面通常以表格展示课程号、课程名、教师、时间、容量、已选人数。这里值得你仔细观察的一个文件是admin_updatecourseinfo.jsp——更新操作需要把当前记录的数据回显到表单里这里分两种实现一种方式是在 Servlet 里request.setAttribute(course, courseObj)再forward到 JSP然后在表单 value 里用${course.courseName}这种 JSTL 或脚本片段回显。另一种方式是直接在 URL 上拼接参数admin_updatecourseinfo.jsp?id3JSP 再按 id 去查一遍库。前一种更符合 MVC 的模型传递习惯后一种在课设里更常见。我特别想提一下admin_updatecourseinfo.jsp里处理「更新失败回显」的技巧提交给 UpdateCourseServlet 后如果更新失败不能直接跳一个失败页就完事——最负责的写法是把用户填的那份数据重新setAttribute回 requestforward 回去让表单保留用户输入。但课设通常只会sendRedirect(updatefail.jsp)这样用户填的内容全丢了还得重新输一遍。这个细节是区分「能跑」和「好用的课设」的分水岭。4.3 从 JSP 命名看错误处理覆盖范围把 RAR 包里的 JSP 文件名拉出来看一眼能发现一个规律。失败页有loginfail.jsp、selectcoursefail.jsp、updatefail.jsp、deleteselectedcoursefail.jsp、registerfail.jsp成功页有updatecoursesuccess.jsp、deletesuccess.jsp、deleteusersuccess.jsp、admin_deletecoursesuccess.jsp。说明这套系统对每个写操作都做了「成功/失败」双向出口而不是全部返回同一个页面。这对你自己动手改项目有借鉴意义当你新增一个功能时至少要考虑三条路径——成功跳哪、失败跳哪、参数缺失跳哪。很多人在做课设时只关心成功路径结果答辩时老师故意输错密码或者传个空值页面直接 500这才是最尴尬的翻车现场。这个项目至少把基本的成功失败都覆盖了。另外注意deleteusersuccess.jsp和admin_deletecoursesuccess.jsp的区别前者是用户删除成功的提示页后者是课程删除成功的提示页两个页面独立存在。这说明作者在设计页面的粒度时没有偷懒到共用一个提示页而是按业务场景拆开。虽然这会让文件数量膨胀但对刚接触 JSP 的人来说反而更容易读懂每个操作对应的结果。5. 避坑与常见问题部署到 Tomcat 后最容易翻车的七个点5.1 中文乱码JSP、Servlet、数据库三层各有一道坎现象注册用户填的是中文姓名存入数据库后变成???或者从数据库查出来在页面上显示为乱码。原因JSP 页面声明、Servlet 取值、JDBC URL、数据库表字符集四层只要有一层不是 UTF-8就会断在半路。最常见的组合错误是 JSP 页面用了contentTypetext/html; charsetutf-8但 Servlet 里没有设置request.setCharacterEncoding(utf-8)导致request.getParameter()在解码时用了服务器默认的 ISO-8859-1中文直接变成乱码。解决在 Servlet 里处理任何请求参数的入口处统一加上request.setCharacterEncoding(utf-8)。另外确认 MySQL 建表语句里DEFAULT CHARSETutf8JDBC URL 带上useUnicodetruecharacterEncodingutf-8。三处都对了页面、控制台、数据库三端才会同时对。别只调一处就去试那是玄学调试。5.2 Tomcat 部署后 404Context Path 和 JSP 绝对路径冲突现象项目启动成功Tomcat 管理页面能看到myweb应用但浏览器访问http://localhost:8080/myweb/index.jsp时某些链接点进去 404检查文件明明存在。原因JSP 页面里用了绝对路径比如a href/myweb/browsecourseinfo.jsp但如果你把工程改名导入或者部署时配置的 context path 变了这个硬编码路径和新上下文对不上。还有一种情况是跳转用了response.sendRedirect(/browsecourseinfo.jsp)少了项目名前缀路径直接从根目录解析。解决所有跳转和表单 action 不要写死绝对路径用request.getContextPath() /browsecourseinfo.jsp拼。如果要在 JSP 里拼接可以这样% String path request.getContextPath(); % form action%path%/register methodpost这样无论部署在什么上下文都能正确匹配。这个话题现在热词里频繁出现属于 Java 面试里 JSP 和 Servlet 基础题的经典考法值得养成习惯。5.3 MySQL 8.x 驱动加载失败ClassNotFoundException现象Tomcat 启动后访问登录接口控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因DBHelper里写的是com.mysql.jdbc.Driver但 MySQL 8.x 的驱动类已经改名成com.mysql.cj.jdbc.Driver同时驱动包版本也要换成mysql-connector-java-8.0.x以上。另外很多新手把驱动 jar 包放到项目里但没检查 WebContent/WEB-INF/lib 下是否真的存在Eclipse 里引了本地 jar 但不部署到 Tomcat运行时找不到。解决先说驱动类名改一行private static final String DRIVER com.mysql.cj.jdbc.Driver;再看 jar 包位置确认WebContent/WEB-INF/lib/下能看到mysql-connector-java-8.0.x.jar。如果你是用 Eclipse 的 Build Path 导入的右键 jar 选择Build Path - Add to Build Path还不够要确认部署配置里勾选了这个 jar。最稳的方式是直接把它拖到WEB-INF/lib下Tomcat 必定加载。5.4 验证码图片不显示或刷新无效现象登录页的验证码图片是裂开的或者点击刷新后图片不变化。原因randomImage.jsp在输出图片时如果前面有空白行或out.println输出会导致响应流混入非图片数据图片无法渲染。另一个常见问题是浏览器缓存——同一 URL 的图片会被缓存刷新也不重新拉取。解决在random.jsp里给 img 标签加一个时间戳参数img srcrandomImage.jsp?time%System.currentTimeMillis()%这样每次请求的 URL 都不同浏览器不会命中缓存。对于图像流污染问题确保randomImage.jsp开头没有多余空行且不要在ImageIO.write之后再做任何输出。5.5 部署后 Session 一直失效web.xml 里 session-timeout 没配置现象用户登录后操作几下就被踢回登录页尤其是选课操作来回跳转多个页面时频繁丢失登录态。原因Tomcat 默认 Session 超时时间是 30 分钟如果用户操作间隔比较久确实会失效。但更常见的原因是部署上下文重启或代码 reload 导致 Session 失效这在 Eclipse 的 Debug 模式里很常见——你改了 Servlet 源码Tomcat 自动重载Session 直接丢失。解决在web.xml里显式声明 Session 超时时间单次调试和演示场景下可以设长一点session-config session-timeout60/session-timeout /session-config单位是分钟这里设 60 分钟足够演示完整个选课流程。注意这个字段写在web.xml根元素web-app内别放错位置。5.6 登录成功但跳转页面样式全丢现象登录成功后进入userindex.jsp页面结构还在但图片、 CSS 全部加载失败。原因排除网络问题后大概率是 JSP 页面引用的图片或 CSS 路径写成了相对路径在登录页根目录下能正常加载但跳转到子路径页面后相对路径解析的基准变了导致资源找不到。解决在所有 JSP 页面的 head 区用绝对上下文路径引用资源link relstylesheet href%request.getContextPath()%/css/style.css img src%request.getContextPath()%/img/logo.png这样无论当前 URL 是login.jsp还是admin_updateuserinfo.jsp资源路径都从应用根目录开始解析不会翻车。这也是所有 Java Web 课程设计里最简单但最「一眼定水平」的细节——用了这个写法的通常代码规范意识都过关。5.7 选课成功但已选人数不增加或出现负数现象学生选课成功后回到课程列表该课程的已选人数没有变化或者退课后人数不减。原因这说明选课和更新人数是两个独立的 SQL 操作中间没有包在事务里。选课成功、更新人数失败或者更新字段写错比如selected_count ?但参数顺序传反都会出现数据不一致。解决如果只是课程设计最简单的做法是把两个 SQL 操作包在一个 Service 方法里用同一个 Connection失败就整体回滚。具体结构参考Connection conn null; try { conn DBHelper.getConnection(); conn.setAutoCommit(false); // 1. 插入选课记录 // 2. 更新课程已选人数 已选人数 1 conn.commit(); } catch (Exception e) { if (conn ! null) conn.rollback(); e.printStackTrace(); } finally { // 关闭资源 }这样起码保证同一事务内两条 SQL 要么都成功、要么都失败。真实系统还需要数据库层面给课程表加锁或使用乐观锁版本号但课设到事务级别已经够用。6. 把选课系统当面试素材用三个值得单独拎出来的改写技巧6.1 用 LoginServlet 当模板理解「参数校验 → 业务调用 → 结果分发」的骨架拿到这份代码后我建议你不要一开始就通读全部文件而是先精读一个最完整的链路。LoginServlet是最合适的一个入口因为它的代码短、没有分页、没有级联操作但 MVC 骨架齐全。protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); String inputCode request.getParameter(code); String sessionCode (String) request.getSession().getAttribute(randomCode); if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { response.sendRedirect(loginfail.jsp); return; } if (sessionCode ! null !sessionCode.equalsIgnoreCase(inputCode)) { response.sendRedirect(loginfail.jsp); return; } User user new UserService().login(username, password); if (user ! null) { request.getSession().setAttribute(user, user); response.sendRedirect(userindex.jsp); } else { response.sendRedirect(loginfail.jsp); } }我故意在写这段时带了两个处理细节用户名空校验放在最前验证码比较用了equalsIgnoreCase——字母大小写不敏感比完全严格匹配的体验更好。login()返回User对象。这个设计比返回 boolean 更实用因为登录成功后页面可能需要展示昵称、角色等信息直接在 Session 里存对象比再查一次库优雅得多。你在面试时可以把这个类拿出来讲请求参数如何流转、Service 层怎么解耦、为什么失败用sendRedirect而不是forward。这些细节比背 Spring 八股文更能体现你写过原生 Servlet。6.2 升级改造点给课程列表加分页和模糊查询当前系统里browsecourseinfo.jsp大概率是直接把所有课程select *出来再用表格平铺展示。对课程设计来说没问题但你可以做一个低成本高回报的改造——加一个分页参数。// CourseService 中的分页查询示意 public ListCourse findCoursesByPage(int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String sql SELECT * FROM course LIMIT ?, ?; // 使用 PreparedStatement 设置参数 offset 和 pageSize }对应的 JSP 页面底部加上上一页、下一页每次跳转时带上currentPage参数。这个改造通常只需要改一个 Servlet 的查询方法加一个 JSP 分页条但带来的观感提升是巨大的——从「课设作业」直接变成「像样的小项目」。如果再加一个课程名关键词的模糊查询把 SQL 改成WHERE course_name LIKE ?并传%关键词%那连答辩时的「你还有哪些扩展想法」都能应对。6.3 从 MVC 角度复盘整个资源的价值边界把整个myweb项目完整跑通之后你应该形成自己的判断这版代码的价值不在于功能多强大而在于它是一个纯粹的 MVC 教学样本没有任何框架语法糖每一层你都能直接看到调用关系。我自己的习惯是拿到这类资源先做三件事第一把所有 JSP 文件名列出来画一张请求流向图理清哪条路径是由哪个 Servlet 接管的第二找到DBHelper把数据库连接参数改成自己本机的配置第三从「学生选课」和「管理员删除课程」这两条主链路各抽一个 Servlet 精读。完整流程走完一次这套系统就归你了。从那以后我每次收到这类课程设计资源都强制自己先把这个流程走一遍——先改库连接、再跑登录、最后才看业务代码不再直接双击打开一堆 JSP 文件看半天还找不到入口。希望帮到你。本文还有配套的精品资源点击获取
返回列表