简介:本资源是一套基于Java Web技术实现的学生成绩管理系统完整项目源码,面向高校计算机专业初学者、Java Web入门学习者及课程设计实践者,解决教育场景中成绩录入、查询、统计与权限管理等核心业务需求。压缩包共36个文件,包含10个Java后端逻辑类、9个JSP动态页面、10张运行界面截图(含首页、录入页、查询页、统计页等)、3张PNG图标及XML配置文件等,整体2.94MB,结构清晰体现MVC分层设计。已有33人下载学习,可直接导入Tomcat运行,配套截图与README.md便于快速理解系统功能与部署流程;代码涵盖Servlet处理、JDBC数据库操作、MySQL表设计及基础权限控制,是掌握Java Web开发全流程的典型教学案例。
1. 这不是又一个“Java Web期末作业模板”,而是一套可上线、可维护、可面试复盘的学生成绩管理系统落地路径
很多刚学完 Servlet + JSP 的同学解压基于Java的Web学生成绩管理系统.zip后,第一反应是:页面能点、数据库能连、增删改查都跑通了——然后就扔进回收站。但真实场景里,它暴露的问题远比功能多:登录后跳转错乱、成绩导出 Excel 乱码、MySQL 8.0 驱动报No value specified for parameter 1、Tomcat 9 下 JSP 编译失败提示The method getJspApplicationContext(ServletContext) is undefined……这些不是“小问题”,而是 Java Web 工程化落地的典型断点。本系统本质是一个以Servlet 3.1+ + JDBC + Bootstrap 4 + MySQL 5.7+为技术基线的轻量级管理后台,适合用于课程设计交付、中小教务模块原型验证,或作为 Java 面试中“讲清楚一个完整 Web 项目”的实操锚点。它不依赖 Spring Boot 自动装配,所有控制流、数据流向、异常边界都显式暴露,正因如此,你才能真正看懂request.getRequestDispatcher("/list.jsp").forward(request, response)背后发生了什么,也才敢在面试时被问到“如果登录校验失败,你是怎么中断请求链路的”时,指着Filter类里的chain.doFilter()前后逻辑给出具体代码位置。
2. 从 ZIP 解压到本地可运行:绕过 5 类高频环境陷阱的初始化实操
2.1 解压结构识别与关键文件定位(别急着启动)
ZIP 包内常见目录结构如下(非标准但高概率):
WebStudentScoreSystem/ ├── src/ # Java 源码(含 servlet、dao、model、filter) │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/score/ # 包名,注意是否含中文或空格 │ │ │ ├── controller/ # Servlet 类(如 LoginServlet.java) │ │ │ ├── dao/ # JDBC 操作类(ScoreDao.java) │ │ │ ├── model/ # 实体类(Student.java, Score.java) │ │ │ └── filter/ # 登录过滤器(LoginFilter.java) │ │ └── webapp/ # Web 资源(JSP、CSS、JS、WEB-INF) │ │ ├── index.jsp │ │ ├── login.jsp │ │ ├── list.jsp │ │ └── WEB-INF/ │ │ ├── web.xml # 关键!检查 servlet-mapping 和 filter-mapping │ │ └── lib/ # 可能含 mysql-connector-java-5.1.47.jar 等 ├── build.xml # Ant 构建脚本(若存在,优先用它) └── README.md提示:若
src/main/webapp/WEB-INF/web.xml中<web-app>根元素声明为version="2.3"或2.4,必须升级至3.1(对应 Servlet 3.1 规范),否则 Tomcat 9+ 将拒绝加载。修改后需同步检查web.xml中是否误用已废弃的<jsp-config>冗余配置。
2.2 JDK 与 Tomcat 版本强匹配:为什么 JDK 11 + Tomcat 10 会直接 404
该系统大概率基于 Servlet 3.1 规范开发(由web.xml中version="3.1"或@WebServlet注解推断),因此:
| 组合 | 是否可行 | 原因说明 |
|---|---|---|
| JDK 8 + Tomcat 8.5 | ✅ 推荐 | 官方完全兼容,驱动、编译、运行零冲突 |
| JDK 11 + Tomcat 9 | ⚠️ 需调参 | Tomcat 9 默认启用useBodyEncodingForURI="true",需在conf/server.xml的Connector中显式设为false,否则中文参数乱码 |
| JDK 17 + Tomcat 10 | ❌ 拒绝 | Tomcat 10 迁移至 Jakarta EE 9,所有javax.*包名已改为jakarta.*,原javax.servlet.*类全部找不到 |
实操命令(验证 JDK/Tomcat 兼容性):
# 查看当前 JDK 版本(必须输出 1.8.x) java -version # 查看 Tomcat 版本及 Servlet 规范支持(输出应含 "Servlet 3.1") $CATALINA_HOME/bin/version.sh # 强制指定 JDK 8 启动 Tomcat(避免系统默认 JDK 干扰) export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 $CATALINA_HOME/bin/startup.sh2.3 MySQL 驱动与连接字符串适配:解决Communications link failure和InvalidStateError
系统通常使用mysql-connector-java-5.1.47.jar(JDK 8 兼容),但若你本地是 MySQL 8.0+,必须更换驱动并更新 URL:
| MySQL 版本 | 推荐驱动版本 | JDBC URL 示例(关键参数) |
|---|---|---|
| 5.7 | mysql-connector-java-5.1.47.jar | jdbc:mysql://localhost:3306/scoredb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 |
| 8.0+ | mysql-connector-java-8.0.33.jar | jdbc:mysql://localhost:3306/scoredb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false |
关键参数说明:
serverTimezone=Asia/Shanghai:MySQL 8.0+ 强制要求时区,否则抛The server time zone value 'XXX' is unrecognizedallowPublicKeyRetrieval=true:解决Public Key Retrieval is not allowed错误useSSL=false:本地开发可关闭 SSL(生产环境必须开启并配置证书)
验证数据库连接的最小 Java 测试代码(放在src/test/java/DbTest.java):
import java.sql.*; public class DbTest { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/scoredb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false"; String user = "root"; String pwd = "your_password"; try (Connection conn = DriverManager.getConnection(url, user, pwd)) { System.out.println("✅ 数据库连接成功!"); try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT VERSION()")) { if (rs.next()) { System.out.println("MySQL 版本: " + rs.getString(1)); } } } catch (SQLException e) { System.err.println("❌ 连接失败: " + e.getMessage()); // 重点捕获:Communications link failure / Access denied / Unknown database } } }2.4 WEB-INF/web.xml 配置核验:3 个必改项与 1 个隐藏陷阱
打开web.xml,逐行检查以下 4 处(90% 的 404/500 错误源于此):
| 位置 | 原始内容(典型错误) | 正确写法 | 为什么重要 |
|---|---|---|---|
<servlet-class> | com.example.score.LoginServlet | 确保包路径与实际.java文件物理路径完全一致,大小写敏感 | 类找不到(ClassNotFoundException)直接 500 |
<url-pattern> | /login | 必须以/开头,且与 JSP 表单action="/login"严格一致 | URL 不匹配导致表单提交到根路径,404 |
<filter-mapping> | <url-pattern>/*</url-pattern> | ✅ 正确,但需确认<filter-class>指向LoginFilter且其doFilter()中request.getRequestURI()判断逻辑无硬编码路径 | 过滤器失效 → 未登录可直访/list.jsp |
<welcome-file-list> | <welcome-file>index.jsp</welcome-file> | 若index.jsp实际重定向到/login.jsp,此处无需改;但若首页是login.jsp,则必须写<welcome-file>login.jsp</welcome-file> | 访问http://localhost:8080/时 404 |
注意:若
web.xml中存在<jsp-config>块(如设置<el-ignored>true</el-ignored>),请整块删除。Servlet 3.1+ 默认启用 EL 表达式,保留该配置会导致 JSP 中${student.name}不解析,显示为纯文本。
3. 功能闭环验证:从登录到成绩导出的 4 个核心链路调试法
3.1 登录链路:用浏览器开发者工具抓取 3 层 HTTP 状态流转
登录不是“点一下就进去了”,而是典型的3 次 HTTP 跳转,每一步都可验证:
第 1 次(POST):
POST /login(表单提交)- 检查 Request Payload:
username=admin&password=123(确认未被 JS 加密) - 检查 Response Headers:
Set-Cookie: JSESSIONID=xxx(确认 Session 已创建)
- 检查 Request Payload:
第 2 次(302 Redirect):
GET /list.jsp(LoginServlet 重定向)- 检查 Response Headers:
Location: /list.jsp(确认重定向地址正确) - 检查 Cookies:
JSESSIONID是否随请求自动携带(浏览器自动行为)
- 检查 Response Headers:
第 3 次(GET):
GET /list.jsp(最终渲染页面)- 检查 Network → Response:HTML 源码中是否含
<table>数据(而非空 table) - 检查 Console:有无
Uncaught ReferenceError: $ is not defined(jQuery 未加载)
- 检查 Network → Response:HTML 源码中是否含
调试技巧:在LoginServlet.java的doPost()结尾处加日志:
System.out.println("✅ Login success, redirecting to /list.jsp, session ID: " + request.getSession().getId()); response.sendRedirect(request.getContextPath() + "/list.jsp");若控制台无输出,说明卡在用户名密码校验环节(查dao.checkLogin()方法)。
3.2 成绩录入链路:解决No value specified for parameter 1的 PreparedStatement 绑定
该错误 100% 源于ScoreDao.java中PreparedStatement的?占位符数量与setXxx()调用次数不匹配。典型错误代码:
// ❌ 错误:SQL 有 4 个 ?,但只 set 了 3 个 String sql = "INSERT INTO score (student_id, subject, score, term) VALUES (?, ?, ?, ?)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, studentId); ps.setString(2, subject); ps.setDouble(3, score); // ❌ 漏掉第 4 个 term 参数!正确写法(带防御性检查):
String sql = "INSERT INTO score (student_id, subject, score, term) VALUES (?, ?, ?, ?)"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, score.getStudentId()); ps.setString(2, score.getSubject()); ps.setDouble(3, score.getScore()); ps.setString(4, score.getTerm()); // ✅ 补全 int rows = ps.executeUpdate(); System.out.println("✅ 插入成功,影响行数:" + rows); }提示:在
ScoreDao.java的每个 SQL 操作方法开头,打印完整 SQL(替换?为实际值)便于调试:System.out.println("DEBUG SQL: " + sql.replace("?", "'"+value+"'"));
3.3 成绩列表页:JSP EL 表达式不生效的 2 种根因与修复
若list.jsp中<c:forEach items="${scoreList}" var="s">循环不执行,或${s.studentName}显示为空,按顺序排查:
| 现象 | 检查点 | 修复操作 |
|---|---|---|
| 页面空白,无表格 | list.jsp顶部是否缺失<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> | ✅ 添加该指令,且确认WEB-INF/lib/下有jstl-1.2.jar(JDK 8 必须用 1.2,1.3+ 需 Jakarta 版本) |
| 表格有行但字段为空 | Score.java中getStudentName()方法是否存在?getter 名称是否与 JSP 中${s.studentName}的studentName严格匹配(驼峰转小写)? | ✅ 检查Score类:private String studentName;→public String getStudentName(){return studentName;} |
快速验证 EL 是否工作(在list.jsp任意位置插入):
<!-- 输出测试 --> <p>EL 测试:${'Hello' + ' World'} </p> <!-- 应显示 Hello World --> <p>Session 测试:${pageContext.session.id}</p> <!-- 应显示 JSESSIONID -->3.4 Excel 导出功能:解决中文乱码与空文件的 POI 4.1.2 兼容方案
系统若用 Apache POI 导出,常见于ExportServlet.java。JDK 8 下必须使用POI 4.1.2(更高版本需 JDK 11+):
| 问题 | 原因 | 修复代码 |
|---|---|---|
Excel 打开显示?? | HSSFWorkbook创建时未设编码,或response.setContentType()错误 | java response.setContentType("application/vnd.ms-excel;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=score_" + new Date().getTime() + ".xls"); |
| 导出文件双击打不开(空文件) | workbook.write(outputStream)后未outputStream.close(),或response.getOutputStream()被提前关闭 | ✅ 在try-with-resources中确保OutputStream和HSSFWorkbook同时关闭 |
最小可运行导出代码段(ExportServlet.java):
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Score> scoreList = new ScoreDao().findAll(); // 获取数据 // 创建 Excel 工作簿 HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("学生成绩"); // 写入表头 HSSFRow headerRow = sheet.createRow(0); String[] headers = {"学号", "姓名", "科目", "成绩", "学期"}; for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 写入数据 int rowNum = 1; for (Score s : scoreList) { HSSFRow row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(s.getStudentId()); row.createCell(1).setCellValue(s.getStudentName()); row.createCell(2).setCellValue(s.getSubject()); row.createCell(3).setCellValue(s.getScore()); row.createCell(4).setCellValue(s.getTerm()); } // 设置响应头(关键!) response.setContentType("application/vnd.ms-excel;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment;filename=score_export.xls"); // 写出流(自动关闭) try (OutputStream out = response.getOutputStream()) { workbook.write(out); } }4. 面试高频考点穿透:从系统代码反推 Java Web 核心机制的 3 个硬核问题
4.1 “登录状态如何保持?”——手写 Session 与 Cookie 协同原理图解
面试官问“你怎么保证用户登录后访问其他页面不重新登录?”,不能只答“用了 Session”。要结合本系统代码指出具体实现点:
- Session 创建时机:在
LoginServlet.java的doPost()中,request.getSession(true)第一次调用即创建(true表示不存在则新建)。 - Cookie 传输载体:Tomcat 自动生成
JSESSIONID=ABC123Cookie,通过Set-Cookie响应头下发,浏览器后续请求自动携带。 - 服务端绑定逻辑:
LoginFilter.java的doFilter()中,request.getSession(false)返回null即表示未登录,此时response.sendRedirect("/login.jsp")中断链路。
画给面试官的简图(文字版):
浏览器 POST /login → Tomcat → LoginServlet → request.getSession(true) → 创建 Session 对象(内存中) ↓ Tomcat 生成 JSESSIONID=ABC123 → Set-Cookie 响应头 → 浏览器存入 Cookie ↓ 浏览器 GET /list.jsp → 自动带 Cookie: JSESSIONID=ABC123 → Tomcat 根据 ID 查找 Session 对象 ↓ LoginFilter → request.getSession(false) != null → 放行 → list.jsp 渲染进阶提示:若被追问“Session 存在哪?集群下怎么共享?”,可答:“默认存在 JVM 堆内存,生产环境用 Redis 存储 Session,通过
HttpServletRequest.getSession().setAttribute()写入,getAttribute()读取,底层由RedisHttpSessionConfiguration管理序列化。”
4.2 “SQL 注入怎么防?”——对比 Statement 与 PreparedStatement 的字节码差异
本系统若用Statement(极危险),LoginDao.java中可能有:
// ❌ 危险!拼接 SQL String sql = "SELECT * FROM user WHERE username='" + username + "' AND password='" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 用户输 ' OR '1'='1 会绕过校验!PreparedStatement 的防护本质:
- 预编译阶段:
conn.prepareStatement("SELECT * FROM user WHERE username=? AND password=?"),数据库将 SQL 解析为执行计划,?占位符被标记为参数而非 SQL 文本。 - 执行阶段:
ps.setString(1, username)时,JDBC 驱动将username值作为纯数据发送,数据库不会将其当 SQL 解析。即使username = "' OR '1'='1",最终执行的仍是WHERE username='\' OR \'1\'=\'1'(字符串字面量)。
验证方式:在ScoreDao.java中故意用Statement执行SELECT * FROM score WHERE subject='数学' OR '1'='1',观察是否查出全表;再换PreparedStatement,结果为空。
4.3 “JSP、Servlet、Filter、Listener 的职责边界是什么?”——用本系统文件树回答
直接指向 ZIP 包内文件,建立清晰映射:
| 技术组件 | 本系统对应文件 | 职责 | 面试话术 |
|---|---|---|---|
| Servlet | LoginServlet.java,ScoreListServlet.java | 处理具体业务请求,如接收表单、调用 DAO、转发到 JSP。它是 MVC 中的 Controller。 | “我负责把用户输入转成 Java 对象,交给 DAO 操作数据库,再决定跳转到哪个页面。” |
| JSP | login.jsp,list.jsp | 生成动态 HTML,专注视图渲染。通过 EL 表达式${scoreList}绑定数据,用 JSTL<c:forEach>遍历。 | “它不是 Java 代码容器,而是 HTML 模板,所有逻辑应在 Servlet 中完成。” |
| Filter | LoginFilter.java | 横切拦截,对所有请求做统一处理。本系统中检查 Session 是否有效,是权限控制的第一道门。 | “它像安检闸机,不关心具体去哪,只管你有没有票(Session)。” |
| Listener | (本系统可能无,但可扩展) | 监听生命周期事件,如ServletContextListener在 Tomcat 启动时初始化数据库连接池。 | “如果我要在系统启动时加载全校专业列表到缓存,就在这里写contextInitialized()。” |
注意:若 ZIP 中无 Listener,可主动提出:“我注意到系统缺少全局初始化逻辑,建议增加
ContextLoaderListener加载DBUtil单例,避免每次 Servlet 都新建 Connection。”
5. 生产就绪加固:3 个让系统从“能跑”到“敢上”的关键改造
5.1 数据库连接池替换:从new Connection()到 HikariCP 的 5 行代码升级
原始系统大概率在DBUtil.java中用DriverManager.getConnection()每次新建连接,这在并发下必然崩溃。必须替换为连接池,推荐 HikariCP(性能最优,Spring Boot 默认):
步骤:
- 下载
hikaricp-5.0.1.jar(JDK 8 兼容)放入WEB-INF/lib/ - 删除
DBUtil.java中所有DriverManager.getConnection()代码 - 新建
HikariConfig.java:
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; public class HikariConfig { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/scoredb?serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false"); config.setUsername("root"); config.setPassword("your_password"); config.setDriverClassName("com.mysql.cj.jdbc.Driver"); config.setMaximumPoolSize(20); config.setMinimumIdle(5); config.setConnectionTimeout(30000); config.setIdleTimeout(600000); config.setMaxLifetime(1800000); dataSource = new HikariDataSource(config); } public static DataSource getDataSource() { return dataSource; } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }- 在
ScoreDao.java中调用:try (Connection conn = HikariConfig.getConnection()) { ... } - 验证效果:用
ab -n 1000 -c 100 http://localhost:8080/list.jsp压测,响应时间应稳定在 20ms 内,无连接超时。
5.2 JSP 编译优化:解决org.apache.jasper.JasperException: Unable to compile class for JSP的 2 个配置
Tomcat 9+ 对 JSP 编译更严格,常见于list.jsp中 EL 表达式或标签库问题:
conf/web.xml全局配置(推荐):
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> <!-- 禁用外部编译进程 --> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> <!-- 关闭 X-Powered-By 头 --> </init-param> <init-param> <param-name>compilerSourceVM</param-name> <param-value>1.8</param-value> <!-- 强制 JDK 8 编译 --> </init-param> <init-param> <param-name>compilerTargetVM</param-name> <param-value>1.8</param-value> </init-param> </servlet>JSP 页面级修复(快速生效): 在list.jsp顶部添加 page 指令:
<%@ page contentType="text/html;charset=UTF-8" language="java" errorPage="error.jsp" isELIgnored="false" trimDirectiveWhitespaces="true" %>isELIgnored="false":强制启用 EL(覆盖 web.xml 全局设置)trimDirectiveWhitespaces="true":去除 JSP 指令间的空白,避免 HTML 渲染异常
5.3 安全头加固:防止 XSS 与点击劫持的 4 行 HTTP 响应头
在LoginFilter.java的doFilter()方法中,于chain.doFilter()前添加:
// 防 XSS:强制浏览器以指定编码解析 response.setHeader("Content-Type", "text/html;charset=UTF-8"); // 防 MIME 类型嗅探攻击 response.setHeader("X-Content-Type-Options", "nosniff"); // 防点击劫持:禁止被 iframe 嵌入 response.setHeader("X-Frame-Options", "DENY"); // 防 XSS:启用浏览器内置 XSS 过滤器(旧版 IE/Chrome) response.setHeader("X-XSS-Protection", "1; mode=block");验证方式:
- 启动系统,用浏览器访问
http://localhost:8080/login.jsp - 打开开发者工具 → Network → 点击任意请求 → Headers → Response Headers
- 确认上述 4 个 Header 均存在且值正确
提示:若需允许特定域名嵌入(如学校官网集成),将
DENY改为ALLOW-FROM https://school.edu.cn,但必须确保该域名自身安全。
本文还有配套的精品资源,点击获取