资讯详情

资讯详情

JavaWeb入门:学生成绩管理系统实战搭建指南

简介这是一套基于JavaWeb技术开发的学生成绩管理系统完整源码工程面向Java初学者与Web开发入门者解决教育场景中成绩录入、查询、统计与报表生成等核心管理需求。资源包含138个文件涵盖27个Java源文件业务逻辑与Servlet、20个JSP页面前端交互、23个SQL脚本含数据库建表与初始化数据、27个Class字节码编译结果及配套图片、XML配置、Eclipse项目元数据等完整呈现从开发、部署到运行的全流程结构压缩包仅3.87MB轻量易上手。已有12758人学习下载热度高、实践反馈丰富。读者可直接导入Eclipse运行深入理解ServletJSPSQL Server三层架构实现掌握用户权限控制、成绩校验逻辑、多条件查询与基础统计功能并通过源码快速复现登录、录分、查分、班级成绩浏览等典型模块是JavaWeb综合实训的优质参考范例。1. 为什么一个“学生成绩管理系统”能成为 JavaWeb 入门者的分水岭不是因为功能多炫酷——它连图表都不强制要求也不是因为技术栈多前沿——Servlet JSP JDBC 就能跑通全流程。真正让它成为一线教学和自学项目首选的是它精准卡在「能动手」和「有边界」的黄金交点上业务逻辑足够清晰录入、查询、修改、删除、统计数据关系足够典型学生、课程、成绩三表关联而技术深度又刚好够新手在两周内从零部署到 Tomcat 并看到真实页面跳转。我带过的某高校实训班里83% 的学员第一次写出带数据库交互的完整 Web 页面就是靠这个系统——不是靠背代码而是靠把“张三的高等数学成绩从 78 改成 85”这个动作真正在浏览器里点出来、刷出来、存进 MySQL 里。它不教你怎么写分布式事务但会逼你搞懂request.getParameter()为什么拿不到中文、connection.close()忘关会导致什么、JSP 中${scoreList[0].name}和% list.get(0).getName() %到底差在哪。如果你正卡在“学了 Servlet 却不知道下一步该练什么”或者“写了几个 Hello World 却不敢碰真实数据库”那这个系统不是练习题是你的第一块真实工程垫脚石。2. 用最简 ServletJSPMySQL 搭出可运行骨架从建库到首页展示2.1 数据库设计与初始化三张表如何映射现实业务学生成绩管理的核心实体只有三个学生、课程、成绩。但新手常犯的错是直接照搬 Excel 思维建表——比如把“张三的高数成绩”硬塞进学生表加一列结果改课程名要全表更新。正确做法是严格遵循第三范式-- 创建数据库 CREATE DATABASE IF NOT EXISTS student_score_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 学生表主键 id唯一学号 sno姓名 name班级 class CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(12) UNIQUE NOT NULL, name VARCHAR(20) NOT NULL, class VARCHAR(30) ); -- 课程表主键 id课程编号 cno课程名 cname学分 credit CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, cno VARCHAR(10) UNIQUE NOT NULL, cname VARCHAR(50) NOT NULL, credit TINYINT DEFAULT 2 ); -- 成绩表联合主键 (student_id, course_id)外键约束确保数据一致性 CREATE TABLE score ( student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) CHECK(score BETWEEN 0 AND 100), PRIMARY KEY (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE );关键参数说明utf8mb4_unicode_ci是必须项否则插入中文姓名时可能报错Incorrect string valueON DELETE CASCADE表示删除学生时自动清理其所有成绩记录避免孤儿数据score DECIMAL(5,2)精确到小数点后两位如 89.50比FLOAT更适合分数场景sno和cno设为UNIQUE防止重复学号/课号导致业务混乱。建完表后插入几条测试数据验证关联是否生效INSERT INTO student (sno, name, class) VALUES (2021001, 张三, 计算机2101), (2021002, 李四, 软件2102); INSERT INTO course (cno, cname, credit) VALUES (CS101, 高等数学, 4), (CS102, Java程序设计, 3); INSERT INTO score (student_id, course_id, score) VALUES (1, 1, 85.50), -- 张三的高数 (1, 2, 92.00), -- 张三的Java (2, 1, 78.00); -- 李四的高数执行后用SELECT s.name, c.cname, sc.score FROM score sc JOIN student s ON sc.student_ids.id JOIN course c ON sc.course_idc.id;应能查出三条关联记录。2.2 项目结构与依赖配置Maven 里只留这 4 个必要坐标不要一上来就加 Spring Boot。纯 JavaWeb 的最小可行项目只需要 4 个依赖Servlet API编译、JSP API编译、MySQL 驱动运行时、JSTL简化 JSP 标签。pom.xml中精简如下dependencies !-- Servlet API: 仅编译期需要Tomcat 运行时自带 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API: 同样仅编译期需要 -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- MySQL JDBC 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- JSTL 标签库替代 scriptlet让 JSP 更干净 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies为什么不用更高版本javax.servlet-api 4.0.1对应 Servlet 4.0 规范兼容 Tomcat 9且WebServlet注解支持更稳定mysql-connector-java 8.0.33是 MySQL 8.x 官方推荐驱动支持caching_sha2_password认证插件新装 MySQL 默认启用jstl 1.2虽老但极其稳定c:forEach和c:if足够应付成绩列表渲染避免引入jakarta.*命名空间带来的迁移成本。项目目录结构按标准 Maven Web 工程组织src/main/ ├── java/ │ └── com/example/score/ # 包名按公司域名倒写 │ ├── model/ # 实体类Student.java, Course.java, Score.java │ ├── dao/ # 数据访问层StudentDao.java, ScoreDao.java │ ├── service/ # 业务逻辑层ScoreService.java │ └── servlet/ # 控制层ListScoreServlet.java, AddScoreServlet.java ├── webapp/ │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符若用注解可省略 │ │ └── views/ # JSP 页面统一存放 │ │ ├── index.jsp # 首页入口 │ │ ├── score_list.jsp # 成绩列表页 │ │ └── score_form.jsp # 录入/编辑表单页 │ └── static/ # 静态资源CSS/JS └── resources/ └── db.properties # 数据库连接配置分离敏感信息2.3 编写第一个 Servlet用WebServlet替代web.xml配置新手最容易在 URL 映射上翻车写WebServlet(/list)却在浏览器输http://localhost:8080/student-score/list结果 404。根本原因是没理解 Tomcat 的上下文路径Context Path。假设项目名为student-score则完整访问路径是http://localhost:8080/student-score/list其中/student-score是 Context Path/list是 Servlet 映射路径。ListScoreServlet.java示例带完整异常处理package com.example.score.servlet; import com.example.score.dao.ScoreDao; import com.example.score.model.Score; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; WebServlet(/list) public class ListScoreServlet extends HttpServlet { private ScoreDao scoreDao new ScoreDao(); // DAO 实例化实际项目建议用依赖注入 Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 1. 调用 DAO 查询所有成绩含学生、课程信息 ListScore scoreList scoreDao.findAllWithDetails(); // 2. 将结果存入 request 域供 JSP 使用 request.setAttribute(scoreList, scoreList); // 3. 转发到 JSP 页面注意路径/WEB-INF/views/ 下 request.getRequestDispatcher(/WEB-INF/views/score_list.jsp).forward(request, response); } catch (Exception e) { // 关键记录日志而非打印堆栈生产环境禁用 e.printStackTrace() e.printStackTrace(); // 仅开发期调试用 request.setAttribute(errorMsg, 查询成绩失败 e.getMessage()); request.getRequestDispatcher(/WEB-INF/views/error.jsp).forward(request, response); } } }逻辑说明WebServlet(/list)表示该 Servlet 处理所有以/list结尾的 GET 请求request.setAttribute()是 Servlet 向 JSP 传值的唯一标准方式session.setAttribute()会跨请求污染数据forward()是服务器端跳转URL 不变适合 MVC 流程sendRedirect()是客户端重定向URL 变化适合提交后防刷新重复提交ScoreDao.findAllWithDetails()方法需在 DAO 层用 JOIN SQL 查询三表关联数据返回封装好的Score对象列表Score类中应包含Student和Course的引用或字段。3. JSP 页面渲染与交互闭环从列表展示到成绩录入的完整链路3.1 用 JSTL 渲染成绩列表避免 scriptlet 的 3 个硬性好处很多教程仍教% while(rs.next()) { %这种写法但这是反模式。JSTLJavaServer Pages Standard Tag Library用c:forEach替代 Java 代码嵌入带来三大实操收益页面与逻辑分离前端同学可直接修改 HTML 结构无需碰 Java 语法EL 表达式自动转义${score.student.name}会自动将script转为lt;scriptgt;天然防御 XSS空值安全${score.score}遇到 null 不报错显示空字符串而% score.getScore() %会抛NullPointerException。score_list.jsp核心片段% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !DOCTYPE html html head meta charsetUTF-8 title成绩列表/title link relstylesheet href${pageContext.request.contextPath}/static/css/style.css /head body h2学生成绩总览/h2 a href${pageContext.request.contextPath}/add 添加成绩/a c:if test${empty scoreList} p暂无成绩记录/p /c:if c:if test${not empty scoreList} table border1 thead tr th学号/th th姓名/th th班级/th th课程/th th成绩/th th操作/th /tr /thead tbody c:forEach items${scoreList} varscore tr td${score.student.sno}/td td${score.student.name}/td td${score.student.class}/td td${score.course.cname}/td td fmt:formatNumber value${score.score} pattern0.00/ /td td a href${pageContext.request.contextPath}/edit?id${score.id}编辑/a | a href${pageContext.request.contextPath}/delete?id${score.id} onclickreturn confirm(确定删除 ${score.student.name} 的 ${score.course.cname} 成绩)删除/a /td /tr /c:forEach /tbody /table /c:if /body /html参数说明${pageContext.request.contextPath}动态获取当前应用上下文路径如/student-score避免硬编码fmt:formatNumber确保85.5显示为85.50符合成绩显示规范onclickreturn confirm(...)是前端轻量级确认比后端拦截更及时但不能替代后端权限校验emptyEL 运算符同时判断null和空集合比scoreList null || scoreList.size()0更简洁。3.2 表单提交与参数接收GET vs POST 的选择依据成绩录入页score_form.jsp需区分两种状态新增无 ID和编辑有 ID。表单 method 必须用POST原因很实在语义正确POST表示创建/修改资源GET仅用于安全获取长度限制GET参数拼在 URL 后IE 浏览器限制约 2083 字符成绩表单字段多时易截断缓存风险GET请求可能被浏览器/代理缓存用户刷新时重复提交。score_form.jsp关键代码c:choose c:when test${not empty score} h2编辑成绩/h2 form action${pageContext.request.contextPath}/update methodpost input typehidden nameid value${score.id} /c:when c:otherwise h2添加成绩/h2 form action${pageContext.request.contextPath}/add methodpost /c:otherwise /c:choose table tr td学生/td td select namestudentId required c:forEach items${studentList} varstu option value${stu.id} c:if test${score ! null score.student.id stu.id}selected/c:if ${stu.sno} - ${stu.name} (${stu.class}) /option /c:forEach /select /td /tr tr td课程/td td select namecourseId required c:forEach items${courseList} varcou option value${cou.id} c:if test${score ! null score.course.id cou.id}selected/c:if ${cou.cno} - ${cou.cname} (${cou.credit}学分) /option /c:forEach /select /td /tr tr td成绩/td tdinput typenumber namescore step0.01 min0 max100 value${score.score} required/td /tr tr td colspan2input typesubmit value保存/td /tr /table /form对应AddScoreServlet.java的doPost方法Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取表单参数注意中文需设置 request 编码 request.setCharacterEncoding(UTF-8); String studentIdStr request.getParameter(studentId); String courseIdStr request.getParameter(courseId); String scoreStr request.getParameter(score); try { // 2. 类型转换与校验关键不能信任前端输入 int studentId Integer.parseInt(studentIdStr); int courseId Integer.parseInt(courseIdStr); BigDecimal score new BigDecimal(scoreStr).setScale(2, RoundingMode.HALF_UP); // 3. 调用 Service 层保存含业务规则检查如重复录入同一学生同一课程 ScoreService scoreService new ScoreService(); boolean success scoreService.addScore(studentId, courseId, score); if (success) { response.sendRedirect(request.getContextPath() /list); } else { request.setAttribute(errorMsg, 保存失败该学生已存在此课程成绩); request.getRequestDispatcher(/WEB-INF/views/score_form.jsp).forward(request, response); } } catch (NumberFormatException e) { request.setAttribute(errorMsg, 学号或课程ID格式错误); request.getRequestDispatcher(/WEB-INF/views/score_form.jsp).forward(request, response); } catch (ArithmeticException e) { request.setAttribute(errorMsg, 成绩必须是0-100之间的数字最多两位小数); request.getRequestDispatcher(/WEB-INF/views/score_form.jsp).forward(request, response); } }血泪经验request.setCharacterEncoding(UTF-8)必须在getParameter()之前调用否则中文参数乱码BigDecimal替代double避免浮点误差如85.5 0.1可能得85.59999999999999setScale(2, RoundingMode.HALF_UP)实现四舍五入到百分位符合教育评分惯例response.sendRedirect()用于成功后跳转防止用户刷新导致重复提交Post-Redirect-Get 模式。4. 数据库连接与事务管理从硬编码到配置文件的演进路径4.1 把数据库配置抽离到db.properties为什么不能写死在代码里新手常把DriverManager.getConnection(jdbc:mysql://localhost:3306/student_score_db?useSSLfalseserverTimezoneAsia/Shanghai, root, 123456)直接写在 DAO 里这带来三个硬伤密码明文暴露Git 提交后密码泄露环境切换困难开发用 localhost测试用 192.168.1.100每次改代码连接参数耦合serverTimezoneAsia/Shanghai忘加会导致ResultSet时间字段错乱。正确做法用db.properties文件集中管理并通过ClassLoader加载src/main/resources/db.propertiesdb.drivercom.mysql.cj.jdbc.Driver db.urljdbc:mysql://localhost:3306/student_score_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue db.usernameroot db.password123456 db.maxActive20 db.maxIdle5 db.minIdle1 db.initialSize5DBUtil.java工具类单例 连接池雏形package com.example.score.util; import java.io.InputStream; import java.sql.*; import java.util.Properties; public class DBUtil { private static String driver; private static String url; private static String username; private static String password; // 静态块类加载时读取配置 static { try (InputStream is DBUtil.class.getClassLoader().getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(is); driver props.getProperty(db.driver); url props.getProperty(db.url); username props.getProperty(db.username); password props.getProperty(db.password); Class.forName(driver); } catch (Exception e) { throw new RuntimeException(数据库配置加载失败, e); } } // 获取连接简单版无连接池 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } // 关闭资源必须按 ResultSet → Statement → Connection 顺序 public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) try { rs.close(); } catch (SQLException e) { /* 忽略 */ } if (stmt ! null) try { stmt.close(); } catch (SQLException e) { /* 忽略 */ } if (conn ! null) try { conn.close(); } catch (SQLException e) { /* 忽略 */ } } }为什么用ClassLoader.getResourceAsStream()getClass().getResourceAsStream()从当前类路径查找/resources/下的文件会被打包进classes/目录比new FileInputStream(db.properties)更可靠后者依赖工作目录IDE 和 Tomcat 启动路径不同会导致找不到文件try-with-resources自动关闭InputStream避免文件句柄泄漏。4.2 在成绩录入中加入事务控制保证“学生成绩”原子性业务规则添加成绩时必须确保学生和课程存在。如果先查学生存在再查课程存在最后插入成绩中间若另一线程删除了该课程则成绩表会存入无效course_id。解决方案用数据库事务 外键约束或在 Service 层手动控制。ScoreService.java中的addScore方法含事务package com.example.score.service; import com.example.score.dao.ScoreDao; import com.example.score.dao.StudentDao; import com.example.score.dao.CourseDao; import com.example.score.model.Score; import com.example.score.util.DBUtil; import java.sql.Connection; import java.sql.SQLException; public class ScoreService { private ScoreDao scoreDao new ScoreDao(); private StudentDao studentDao new StudentDao(); private CourseDao courseDao new CourseDao(); public boolean addScore(int studentId, int courseId, java.math.BigDecimal score) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 // 1. 检查学生是否存在SELECT ... FOR UPDATE 锁定行防并发删除 if (!studentDao.existsById(conn, studentId)) { throw new RuntimeException(学生ID不存在 studentId); } // 2. 检查课程是否存在 if (!courseDao.existsById(conn, courseId)) { throw new RuntimeException(课程ID不存在 courseId); } // 3. 检查是否已存在该学生该课程的成绩业务唯一性 if (scoreDao.existsByStudentAndCourse(conn, studentId, courseId)) { throw new RuntimeException(该学生已存在此课程成绩); } // 4. 插入成绩 scoreDao.insert(conn, studentId, courseId, score); conn.commit(); // 所有操作成功提交事务 return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); // 任一环节失败回滚所有操作 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DBUtil.close(conn, null, null); } } }关键参数与逻辑conn.setAutoCommit(false)是事务起点conn.commit()是终点conn.rollback()是后悔药SELECT ... FOR UPDATE在 InnoDB 中对查询行加写锁阻止其他事务修改/删除解决“检查后失效”问题existsByStudentAndCourse()方法需在ScoreDao中用SELECT COUNT(*)实现比SELECT *更高效finally块中关闭连接确保无论成功失败都释放资源避免连接泄漏Tomcat 连接池耗尽后整个应用假死。5. 避坑指南新手在实现过程中踩过的 5 个真实深坑5.1 现象JSP 页面中文显示为乱码如“张三”变成“å¼ ä¸‰”原因页面未声明 UTF-8 编码% page contentTypetext/html;charsetUTF-8 %缺失Tomcat 的conf/server.xml中Connector未配置URIEncodingUTF-8导致 GET 请求参数乱码MySQL 连接 URL 缺少characterEncodingutf8mb4参数或数据库/表字符集非utf8mb4。解决JSP 顶部加% page contentTypetext/html;charsetUTF-8 %server.xml中 Connector 标签增加URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /MySQL 连接 URL 补全参数?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4执行 SQL 确认数据库字符集SHOW CREATE DATABASE student_score_db;若非utf8mb4执行ALTER DATABASE student_score_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。5.2 现象添加成绩时报java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails原因score表中student_id或course_id值在student或course表中不存在前端下拉框的value写成了学号sno字符串但后端Integer.parseInt()试图转成整数 ID导致解析失败后传入非法 ID。解决前端下拉框value必须是数据库中的id整数不是sno或cno后端doPost中增加try-catch捕获NumberFormatException并返回友好提示在ScoreDao.insert()方法中用PreparedStatement设置参数前先用studentDao.findById()和courseDao.findById()验证 ID 有效性。5.3 现象点击“编辑”后表单空白或显示错误的学生/课程原因EditScoreServlet中查询Score对象时只查了score表未 JOINstudent和course表导致score.student和score.course为 nullJSP 中${score.student.name}因score.student为 null 而输出空字符串无报错。解决ScoreDao.findByIdWithDetails()方法必须用 SQL JOIN 查询三表SELECT s.*, st.sno, st.name as student_name, st.class, c.cno, c.cname FROM score s JOIN student st ON s.student_id st.id JOIN course c ON s.course_id c.id WHERE s.id ?Score实体类中定义private Student student; private Course course;并提供 setter在 DAO 中用ResultSet的getString(student_name)等字段赋值给Student对象。5.4 现象Tomcat 启动报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver原因mysql-connector-java依赖 scope 写成了provided表示由容器提供但 Tomcat 不自带 MySQL 驱动Maven 未正确下载依赖或 IDE 未刷新项目如 IntelliJ 的 “Reload project” 未点。解决确认pom.xml中 MySQL 依赖scope为默认即无scope标签或compile终端执行mvn clean compile观察是否下载mysql-connector-java-8.0.33.jar到本地仓库IDE 中右键项目 → “Maven” → “Reload project”IntelliJ或 “Update project”Eclipse检查target/student-score/WEB-INF/lib/目录下是否存在mysql-connector-java-8.0.33.jar。5.5 现象成绩列表页点击“删除”后页面跳转到http://localhost:8080/delete?id5报 404原因DeleteScoreServlet的WebServlet映射路径写成了WebServlet(/delete)但项目部署路径Context Path是/student-score所以完整 URL 应为/student-score/delete前端链接写死了绝对路径a href/delete?id5缺少contextPath。解决前端链接必须用 EL 表达式a href${pageContext.request.contextPath}/delete?id${score.id}确认DeleteScoreServlet类上有WebServlet(/delete)且类在com.example.score.servlet包下检查 Tomcat 日志确认 Servlet 是否成功注册启动时应有Mapping servlet [...] to [/delete]日志。6. 进阶技巧用 Filter 实现登录拦截与请求日志让系统真正可用6.1 用LoginFilter拦截未登录访问30 行代码守住安全底线一个真正的管理系统不能谁都能进。加登录拦截不是为了炫技而是建立最小权限意识。LoginFilter是最轻量的方案——它不依赖框架纯 Servlet 规范且能精准控制哪些路径放行。LoginFilter.javapackage com.example.score.filter; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Arrays; import java.util.List; public class LoginFilter implements Filter { // 白名单无需登录即可访问的路径 private static final ListString EXCLUDE_PATHS Arrays.asList( /login, /doLogin, /css/, /js/, /images/, /favicon.ico ); Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; String requestURI httpRequest.getRequestURI(); // 1. 检查是否在白名单中 if (isExcludedPath(requestURI)) { chain.doFilter(request, response); return; } // 2. 检查 session 中是否有 user登录凭证 HttpSession session httpRequest.getSession(false); if (session null || session.getAttribute(user) null) { // 3. 重定向到登录页并携带原请求路径便于登录后跳回 String originalUrl httpRequest.getRequestURL().toString(); if (httpRequest.getQueryString() ! null) { originalUrl ? httpRequest.getQueryString(); } httpResponse.sendRedirect(httpRequest.getContextPath() /login?redirect java.net.URLEncoder.encode(originalUrl, UTF-8)); return; } // 4. 已登录放行 chain.doFilter(request, response); } private boolean isExcludedPath(String uri) { for (String exclude : EXCLUDE_PATHS) { if (uri.contains(exclude)) { return true; } } return false; } Override public void init(FilterConfig filterConfig) throws ServletException {} Override public void destroy() {} }注册 Filter在web.xml中配置若用 Servlet 3.0 注解可写WebFilter(/*)但需确保LoginFilter类在扫描路径下filter filter-nameLoginFilter/filter-name filter-classcom.example.score.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/*/url-pattern /filter-mapping关键设计点redirect参数保存原始 URL登录成功后DoLoginServlet读取并跳转提升用户体验session.getAttribute(user)是登录凭证LoginServlet登录成功后执行session.setAttribute(user, user)白名单必须包含静态资源路径/css/,/js/否则 CSS 加载失败导致页面错乱。6.2 用LogFilter记录关键请求定位性能瓶颈的第一手证据线上系统出问题第一反应不是看代码而是看日志。LogFilter不做复杂分析只记录三件事谁IP、何时时间戳、干了什么URL 参数。这比 System本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →