JSP入门实战:学生信息管理系统开发全记录与高频问题解析
2026/9/15 23:57:28 网站建设 项目流程

很多朋友一听到 JSP 三个字母,第一反应是:这都什么年代了还学这玩意儿?说实话,我在带项目新人的时候也一度这么想。但真正上手做完一个完整项目才发现,JSP 作为 Java Web 入门的老牌技术,它在教学体系里的位置至今没有同类替代品——不是因为 JSP 多先进,而是因为它把"网页怎么和后端代码打通"这件事,以最直观的方式暴露在开发者面前。

这篇文章不是课程文档,是我基于一个完整的 JSP 入门实战项目——学生信息管理系统——整理出来的全过程记录。项目虽然叫"学生信息管理系统",但我刻意把它做成了一种可复用的骨架:你会看到怎么搭建环境、怎么写页面、怎么做 JDBC 数据交互、怎么理解 JSP 编译机制,还会单独拆解几个高频搜索里大家都在问的细节——JSONArray 怎么引入、离开页面的提示怎么屏蔽、文件夹选择怎么做、编译后的 class 文件到底存在哪。适合刚学完 Java 基础、准备用 JSP 做第一个完整项目的同学,也适合那些因为维护老系统被迫重新捡起 JSP 的朋友。

1. 为什么 2024 年了还要写 JSP:这门老技术的入门价值

1.1 一个真实的尴尬场景

上个月有个读者私信我,说公司有个 2015 年上线的内部管理系统,用的就是 JSP + Servlet + Tomcat 6。他接手之后第一件事就是懵:双击 JSP 文件浏览器打开是一片空白,加了一行 Java 代码报错连个具体行号都看不懂,最后连那个离开页面时的"确定要离开吗"弹窗都不知道该去哪改。

这个场景太典型了。现在的教程都在讲 Spring Boot + Vue 前后端分离,没人教你怎么对付老项目。但现实是:国内大量中小企业的内部系统、高校的课程设计、外包交付的遗留代码,JSP 存量非常大。你可以不靠它找工作,但你得有能力看明白它。

1.2 JSP 能帮你搞懂哪些 Java Web 底层机制

抛开"过时"的成见,JSP 作为入门项目有几个不可替代的价值。

第一,它能让你直观理解"动态网页"到底是什么。JSP 本质上是在 HTML 里嵌入 Java 代码,服务端把代码执行完再把纯 HTML 扔给浏览器。这个"服务端执行"和"客户端渲染"的边界,是后面学所有 Web 框架的地基。

第二,它能帮你吃透 Servlet 生命周期。每个 JSP 页面最终都会被翻译成一个 Servlet 类,翻译、编译、加载、实例化、调用_jspService()方法,这一套流程你亲眼看过编译产物之后就不会再忘。

第三,它能训练你排查问题的基本功。JSP 项目没有 Spring Boot 那么完善的自动配置,很多问题都要手动去 Tomcat 目录里翻日志、看编译文件。这种"手动挡"的调试习惯,恰恰是很多直接上手框架的人缺失的。

所以我的结论很明确:如果你是 Java 初学者,与其直接冲进 Spring Boot 的汪洋大海,不如先花两周做一个 JSP 实战项目。框架会帮你屏蔽复杂,但也屏蔽了理解。

2. 实战项目立项:做一个"学生信息管理系统"需要准备什么

2.1 项目功能边界与页面规划

很多入门者做项目喜欢"贪大",一上来就想做权限管理、角色体系、多租户,结果查资料的时间比写代码的时间还多。我的建议是:第一个 JSP 项目把范围控制在一个清晰的小闭环里。

我做的这个学生信息管理系统,功能边界只有四个:

  • 学生信息的列表展示
  • 新增学生(表单提交)
  • 编辑与删除(带操作确认)
  • 按照姓名或学号模糊查询

对应的页面就是三张:list.jsp(列表 + 查询)、add.jsp(新增表单)、edit.jsp(编辑表单)。其中列表页我会单独做一个"个人信息展示卡片视图"的变体,用来讲解 JSP 里循环渲染的两种不同写法。功能虽然简单,但覆盖了 JSP 开发的所有核心环节:请求转发、参数接收、JDBC 操作、循环与条件渲染、乱码处理、编译机制。

项目的目录结构我直接用了 Eclipse Dynamic Web Project 的标准布局:

StudentManager/ ├── src/ │ ├── com/student/entity/Student.java │ ├── com/student/dao/StudentDao.java │ └── com/student/util/DBUtil.java ├── WebContent/ │ ├── list.jsp │ ├── add.jsp │ ├── edit.jsp │ └── WEB-INF/ │ ├── web.xml │ └── lib/

提示:不要把 JSP 页面直接扔到 WEB-INF 外面就完事。WEB-INF 下的页面不能通过 URL 直接访问,必须经过 Servlet 转发,这是一种安全控制手段。但在入门阶段,为了少走弯路,JSP 放在 WebContent 根目录、直接用浏览器访问是可以接受的。

2.2 环境搭建的版本选择

环境这块我用的是经典三件套,刻意避开了最新版本,原因是兼容性资料最好找:

  • JDK 1.8(没错,还在用 8,老项目最稳的就是它)
  • Tomcat 9.0 或者 Tomcat 8.5(Servlet 4.0 规范,支持 JSP 2.3)
  • Eclipse IDE for Enterprise Java and Web Developers(或者 IDEA 社区版加 Smart Tomcat 插件)

这几样你只要保证 JDK 装好、JAVA_HOME环境变量配好、Eclipse 里能正常启动 Tomcat 就行。有一个特别容易踩的坑:Tomcat 的运行环境(Runtime Environment)必须关联到本地 JDK,而不是 JRE。在 Eclipse 的 Window → Preferences → Server → Runtime Environments 里选择你安装的 JDK 路径,否则 JSP 编译的时候可能出 "Unable to compile class for JSP" 的报错。这个错我见过太多次了,十有八九是 JRE/JDK 选择的问题。

建完项目,先在WebContent下新建一个最简单的index.jsp,用浏览器访问http://localhost:8080/StudentManager/index.jsp,看到页面输出就算通了。

2.3 数据库设计与连接工具类

数据库我用 MySQL 5.7,建库建表语句非常简单:

CREATE DATABASE student_db DEFAULT CHARACTER SET utf8mb4; CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender CHAR(1) DEFAULT '男', age INT, major VARCHAR(50) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

JDBC 连接工具类走的是最标准的套路,注意看 URL 里的编码参数:

package com.student.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { // 这里写你的数据库账号密码 private static final String URL = "jdbc:mysql://localhost:3306/student_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } }

我还是用老版的com.mysql.jdbc.Driver,如果你用的是 MySQL 8+ 的驱动,要换成com.mysql.cj.jdbc.Driver。连接池现在先不引入,入门项目直接DriverManager拿连接足够,等你理解了连接生命周期,再换 Druid 或 HikariCP 不迟。

3. 核心开发:JSP + JDBC 完成页面渲染与增删改查

3.1 DAO 层:数据操作和页面彻底分离

虽然 JSP 允许你在页面里直接写 Java 代码,但绝不建议把 JDBC 连接、数据库查询全堆在 JSP 里。那会让页面变成一坨意大利面,改一个字段要翻半天。我习惯的做法是写一个简单的 DAO 类,把数据访问逻辑全部收敛进去。

以查询全部学生为例:

package com.student.dao; import java.sql.*; import java.util.ArrayList; import java.util.List; import com.student.entity.Student; import com.student.util.DBUtil; public class StudentDao { public List<Student> findAll() { List<Student> list = new ArrayList<>(); String sql = "SELECT * FROM t_student ORDER BY id DESC"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { while (rs.next()) { Student stu = new Student(); stu.setId(rs.getInt("id")); stu.setStudentNo(rs.getString("student_no")); stu.setName(rs.getString("name")); stu.setGender(rs.getString("gender")); stu.setAge(rs.getInt("age")); stu.setMajor(rs.getString("major")); list.add(stu); } } catch (SQLException e) { e.printStackTrace(); } return list; } // 其余 add / update / delete / findByName 方法省略,套路一致 }

JSP 页面里做数据渲染时,先导入java.util.List和我们自己的 DAO,然后调用方法拿数据。这里有一个很多人会漏掉的细节:JSP 页面顶部需要显式声明使用到的类的 import,写法如下:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="java.util.List" %> <%@ page import="com.student.dao.StudentDao" %> <%@ page import="com.student.entity.Student" %>

3.2 列表页的两种循环渲染写法

列表页list.jsp的渲染方式,我建议新手把两种都练一遍。

第一种是经典的<% %>脚本片段 +for循环,这是老代码里最常见的:

<table border="1" cellpadding="8"> <tr> <th>学号</th><th>姓名</th><th>性别</th><th>年龄</th><th>专业</th> </tr> <% StudentDao dao = new StudentDao(); List<Student> students = dao.findAll(); for (Student stu : students) { %> <tr> <td><%= stu.getStudentNo() %></td> <td><%= stu.getName() %></td> <td><%= stu.getGender() %></td> <td><%= stu.getAge() %></td> <td><%= stu.getMajor() %></td> </tr> <% } %> </table>

第二种是用 JSTL 标签,页面干净很多,也是当前维护老项目的主流做法。用 JSTL 需要在WEB-INF/lib下放入jstl.jarstandard.jar,然后在页面头部引入:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:forEach items="${students}" var="stu"> <tr> <td>${stu.studentNo}</td> <td>${stu.name}</td> <td>${stu.gender}</td> <td>${stu.age}</td> <td>${stu.major}</td> </tr> </c:forEach>

注意:使用 JSTL 的${}EL 表达式时,students这个变量必须通过request.setAttribute("students", list)pageContext.setAttribute(...)放进某个作用域,不能像脚本片段那样直接new StudentDao()调方法。

从"看得懂老代码"的角度,你两种都得会;从"自己写新代码"的角度,我推荐 JSTL。这也是我个人的判断:脚本片段只用于入门理解,生产项目里能不用就不用。

3.3 表单提交与中文乱码的两道关卡

新增页面add.jsp核心是一个 form 表单,提交到doAdd.jsp或者一个 Servlet。我用一个专门的doAdd.jsp来处理接收参数和入库,它更直观:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% request.setCharacterEncoding("UTF-8"); String studentNo = request.getParameter("studentNo"); String name = request.getParameter("name"); String gender = request.getParameter("gender"); String ageStr = request.getParameter("age"); String major = request.getParameter("major"); Student stu = new Student(); stu.setStudentNo(studentNo); stu.setName(name); // ... StudentDao dao = new StudentDao(); dao.add(stu); response.sendRedirect("list.jsp"); %>

这里最大的坑是中文乱码。乱码本质上是编码不一致导致的,涉及两道关卡:

第一道:请求参数的编码。在拿任何request.getParameter()之前,必须先调用request.setCharacterEncoding("UTF-8")。但这只对 POST 提交有效,GET 请求的参数在 Tomcat 8 之后默认就是 UTF-8,Tomcat 7 及之前需要改server.xml里的URIEncoding="UTF-8"

第二道:响应的编码。页面头部contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"必须同时都写对。pageEncoding是 JSP 文件本身保存时用的编码,contentType是响应给浏览器时的编码。两个不一致,生成的 HTML 就是乱码。

提示:如果你在 JSP 里看到<%@ page contentType="text/html;charset=GBK" %>,那是老项目常见的 GBK 编码,最好统一改成 UTF-8,否则和现代前端工具对接时全是乱码。

3.4 个人信息展示页面的变体实践

热搜词里有个"jsp个人信息展示页面",我在项目中把它实现成了一个"卡片视图"变体。和表格不同的是,卡片视图更强调 JSP 的片段复用能力。

我写了一个student_card.jspf的片段文件,存放单个学生的展示卡片 HTML 结构:

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <div class="card"> <h3><%= stu.getName() %></h3> <p>学号:<%= stu.getStudentNo() %></p> <p>性别:<%= stu.getGender() %></p> <p>年龄:<%= stu.getAge() %></p> <p>专业:<%= stu.getMajor() %></p> </div>

然后在主页面里用<%@ include file="student_card.jspf" %>引入。注意:静态 include 会在编译阶段直接把文件内容嵌入进来,相当于把代码原样复制,所以这个片段里可以直接使用主页面声明的stu变量。如果你是初学者,这个行为可能有点反直觉,但理解了 JSP 编译机制后就会恍然大悟——这也是我为什么把编译机制放在后面专门讲。

4. 深入 JSP 编译机制:class 文件到底生成在哪里

4.1 JSP 是怎么变成 Servlet 的

讲清楚这个问题,热搜里那个"jsp编译class文件保存在哪里"和"web项目配置tomcat后查看jsp编译后的java类"两个问题就一起解决了。

JSP 文件本身不是直接运行的。当浏览器第一次请求一个 JSP 页面时,Tomcat 会经历一个完整的翻译编译流程:

  1. .jsp文件翻译成.java文件(一个 Servlet 类,继承自org.apache.jasper.runtime.HttpJspBase
  2. 用 javac 把.java编译成.class文件
  3. 加载该 class,创建实例
  4. 调用其_jspService()方法,把页面内容作为响应输出

关键在于第 1、2 步生成的中间文件放在哪里。它们并不在你的项目源码目录里,而是在 Tomcat 的工作目录下。默认路径是:

$CATALINA_BASE/work/Catalina/localhost/项目上下文路径/org/apache/jsp/

其中$CATALINA_BASE就是 Tomcat 的根目录。比如我的项目上下文路径是/StudentManager,那么完整路径就是:

D:\apache-tomcat-9.0.80\work\Catalina\localhost\StudentManager\org\apache\jsp\

这个目录下你能看到list_jsp.javalist_jsp.class这类文件,命名规则是"JSP 文件名 +_jsp"。如果 JSP 在子目录下,会相应生成多级包路径,比如admin/userList.jsp对应的就是org/apache/jsp/admin/userList_jsp.java

4.2 work 目录里到底有什么,逐个看

我在实际排查项目时经常打开这个目录,它真的能解释很多诡异问题。打开list_jsp.java你会发现几件事:

第一,JSP 里写的所有脚本片段,都被原封不动地搬进了_jspService()方法里。

第二,HTML 静态部分变成了out.write()调用,每个标签都可能被拆成多次输出。

第三,JSP 里的声明<%! %>会被翻译成类的成员变量或方法,脚本片段<% %>翻译成方法内的局部代码,表达式<%= %>翻译成out.print()

举个例子,我在 3.4 节说的静态 include 为什么能共享变量?因为打开编译后的list_jsp.java你会发现,被 include 的student_card.jspf内容完全被内联进了_jspService()方法里,所以它当然可以使用主页面作用域里的stu变量。

这个原理对你排查错误非常有用。比如页面报错时 Tomcat 日志里给的行号,有时候指向的是编译后的list_jsp.java的行号,而不是 JSP 源文件的行号。如果你不知道去 work 目录看这个 Java 文件,光靠 JSP 原文件排查会非常痛苦。

4.3 手动触发重新编译的两个手段

搞清楚编译机制后,你还要知道怎么"逼"Tomcat 重新编译 JSP。默认情况下,Tomcat 开启了 JSP 的自动检测机制(development模式),它通过文件时间戳判断 JSP 是否被修改,有修改就会自动重新翻译编译。这也是为什么你改了 JSP 保存后,刷新页面就能看到效果,不需要重启 Tomcat。

但有些情况自动检测会失灵,例如:

  • 文件时间戳没变,但内容变了(比如用 SVN 或 Git 切换分支后,文件的修改时间可能没刷新)
  • 你手动删除了 work 目录里的某个_jsp.class,但.java还在
  • 页面报错后 Tomcat 进入半缓存状态

最快的处理办法就是:关掉 Tomcat,把work/Catalina/localhost/项目名这个目录整个删掉,再重启 Tomcat。第一次访问时它会重新生成全部编译文件。这招我称之为"JSP 三板斧"之一,另外两板斧是mvn clean和重启 IDE 缓存,对付开发期的诡异问题屡试不爽。

4.4 一个容易被忽略的生产部署差异

开发环境里 JSP 是动态编译的,但生产环境很多团队会把 JSP 预编译成 class 文件放进部署包,这样能减少首次访问的延迟,也方便检查语法错误。实现方式是使用 Ant 任务或 Maven 插件调用 JSP 编译器的JspC命令。

不过入门阶段我不建议你玩预编译,先老老实实让 Tomcat 动态编译就好。但有一点要知道:生产环境务必把web.xmlcontext.xml里的开发模式关掉,比如在WEB-INF/web.xml中设置:

<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>development</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>modificationTestInterval</param-name> <param-value>3600</param-value> </init-param> </servlet>

否则生产环境每次访问都会去检查 JSP 文件时间戳,性能差不说,万一有人在服务器上误改了 JSP,线上直接出问题。

5. 三个高频需求落地:JSONArray 引入、离开提示屏蔽、文件夹选择

这部分我直接对应热搜词里大家搜得最多的几个具体问题来写,每个都是真实开发里会碰到的。

5.1 在 JSP 页面里正确引入 JSONArray

热搜词里的"jsp jsonarray import"我太熟了,很多人想在 JSP 里生成 JSON 数据返回给前端,结果引入包的时候一头雾水。JSP 里使用 JSONArray 之前,第一步是搞清楚你用哪一个库。常见的有三种:

库名称包名典型引入坐标
json-libnet.sf.jsonnet.sf.json:json-lib:2.4
org.jsonorg.jsonorg.json:json:20231013
Fastjsoncom.alibaba.fastjsoncom.alibaba.fastjson:fastjson:1.2.83

我推荐新手用 org.json,依赖少,不需要额外引入 commons-beanutils 那一堆东西。把 jar 包放到WEB-INF/lib后,在 JSP 头部这样写 import:

<%@ page import="org.json.JSONArray" %> <%@ page import="org.json.JSONObject" %>

使用示例:

<% List<Student> students = new StudentDao().findAll(); JSONArray arr = new JSONArray(); for (Student stu : students) { JSONObject obj = new JSONObject(); obj.put("id", stu.getId()); obj.put("name", stu.getName()); obj.put("studentNo", stu.getStudentNo()); arr.put(obj); } out.print(arr.toString()); %>

警告:如果你用的是 json-lib(net.sf.json),它依赖commons-langcommons-beanutilscommons-collectionscommons-logging四个包,少一个直接抛NoClassDefFoundError。新手排查不出原因就卡在这。所以首选 org.json 是最省心的。

另一个要注意的是JSONArrayJSONObject的循环引用问题。如果你直接把带双向关联的实体类塞进 JSONObject,会抛出JSONException: JSONArray initial value should be a string or collection or array。解决办法是像上面示例那样手动一个个 put 字段,别图省事直接把对象丢进去。

5.2 屏蔽"离开页面"提示的正确姿势

热搜词里的"屏蔽jsp离开页面提示",对应的场景是:你在 JSP 页面上做了表单,填了一半不小心刷新或关闭,浏览器弹出"确定要离开吗"的提示,而这个提示在某个条件下根本不该出现。

这个提示不是 JSP 的功能,是浏览器原生的beforeunload事件触发的。很多人误以为要改 JSP 后端代码,其实完全在前端处理。如果你的项目里没有主动绑定这个事件,那弹出提示的源头可能是某个第三方 JS 组件或弹窗插件自己注册的。

要屏蔽它,思路是"如果有绑定,就解绑或覆盖"。在 JSP 页面的<head>或页面底部加入:

window.onbeforeunload = null;

如果原代码是用addEventListener绑定的,直接置空onbeforeunload不一定有效,因为addEventListener注册的处理器和onbeforeunload属性是两套独立的机制。这时候你得找到绑定源头,那通常是这样的代码:

window.addEventListener("beforeunload", function (e) { e.preventDefault(); e.returnValue = "确定要离开吗?"; });

要精确移除,需要把回调函数提取成命名函数:

function leaveConfirmHandler(e) { e.preventDefault(); e.returnValue = "确定要离开吗?"; } // 需要开启时绑定 window.addEventListener("beforeunload", leaveConfirmHandler); // 需要屏蔽时移除 window.removeEventListener("beforeunload", leaveConfirmHandler);

还有一种常见需求是:只有表单有改动时才提示,没有改动就不提示。很多实现会把确认提示无条件绑定,导致用户点了一个普通跳转链接也弹窗。正确做法是加一个"脏标记":

let formDirty = false; document.getElementById("myForm").addEventListener("input", function () { formDirty = true; }); window.addEventListener("beforeunload", function (e) { if (formDirty) { e.preventDefault(); e.returnValue = "表单尚未保存,确认离开吗?"; } }); // 保存成功后置回 false function markSaved() { formDirty = false; }

这个方案在涉政、敏感、低俗内容管控场景也通用——凡是涉及重要表单的页面,都建议用"脏标记"来控制提示,而不是无脑绑定。

5.3 JSP 场景下的文件夹选择功能

"jsp选择文件夹"这个问题其实也偏前端,它对应的是 HTML 5 的webkitdirectory属性。传统<input type="file">只能选文件,但加上webkitdirectory后,可以选择整个文件夹,浏览器会把文件夹内所有文件的相对路径作为webkitRelativePath属性暴露出来。

在 JSP 页面里直接写:

<form action="upload.jsp" method="post" enctype="multipart/form-data"> <input type="file" name="folder" webkitdirectory> <button type="submit">上传文件夹</button> </form>

后端接收时要注意,JSP 里处理文件上传不能再用request.getParameter(),因为enctype="multipart/form-data"会让参数以二进制流方式提交。要么用 Servlet 3.0 的@MultipartConfig+request.getPart(),要么用 Commons FileUpload 组件。初学者最容易在这里栽跟头,一提交就发现getParameter()返回 null。

如果你是做老系统的维护,还要注意低版本 IE 完全不支持webkitdirectory,一般要降级为普通文件选择框。可以用简单判断:

if (typeof HTMLInputElement.prototype.webkitdirectory === "undefined") { alert("当前浏览器不支持文件夹选择,请使用 Chrome/Edge 或选择单个文件"); }

6. 实测踩坑记录:新手最容易卡住的六个环节

做这个实战项目的过程中,我把团队新人问过的问题和我自己踩过的坑汇总成了下面的清单,按出现频率排序:

6.1 Tomcat 端口被占用

启动 Tomcat 报Port 8080 required by Tomcat v9.0 Server at localhost is already in use,多半是之前没关干净或者别的程序占了端口。解法不复杂:找到占用进程杀掉,或者改 Tomcat 的server.xml里的端口。但有一件事要留意,server.xml里有三个端口要一起改:HTTP 端口(默认 8080)、AJP 端口(默认 8009)、关闭指令端口(默认 8005)。我见过只改 8080 不改 8005 的,结果下次启动照样冲突。

6.2ClassNotFoundException: com.mysql.jdbc.Driver

这个错误十有八九是 mysql-connector.jar 没有放进WEB-INF/lib。注意:不是放在构建路径里就行,Web 应用运行时用的是WEB-INF/lib下的 jar。Eclipse 里有时候你把 jar 拖进了项目但没复制到 lib 目录,部署的时候就找不到。

6.3 修改了 JSP 但页面没变化

先排除浏览器缓存(Ctrl+F5 强制刷新),再看 Tomcat 的 work 目录下对应的_jsp.java文件时间戳有没有更新。如果时间戳没变,按 4.3 节的办法删 work 目录重启。还有一个小众原因:Eclipse 的自动部署(Auto Reload)没开启,改完 JSP 不会同步到 Tomcat 的 webapps 目录。

6.4 404 页面提示找不到 JSP

检查你访问的 URL 和项目上下文路径是否一致。http://localhost:8080/StudentManager/list.jsp中间那段StudentManager是项目部署名。用 Eclipse 部署时,右键项目 → Properties → Web Project Settings 里的 Context Root 可能和你预期不一致,改了就不会 404。

6.5 500 错误 + "Unable to compile class for JSP"

按堆栈信息找到具体是哪一行 JSP 语法错误。最常见的是<%%>没配对,或者页面里写了System.out.println忘记加分号。记住一点:JSP 里的脚本片段是 Java 代码,语法要求比 HTML 严格得多。

6.6 数据库出现?号乱码

页面显示正常,但插入数据库后中文变成??。这不是页面编码问题,是 JDBC 连接 URL 里少了characterEncoding=utf8,或者数据库表本身不是 utf8 字符集。按 2.3 节的 URL 统一配置,同时用ALTER TABLE t_student CONVERT TO CHARACTER SET utf8mb4;修正已有表。

7. 以这个项目为跳板,下一步怎么走

做完学生信息管理系统,你已经把 JSP 的核心用法过了一遍。接下来我建议做三件递进的事。

第一件:把 DAO 层改成 Servlet + JSP 的标准 MVC 模式。现在你的页面里还直接调用了 DAO,真正的老项目里通常是浏览器请求 Servlet,Servlet 调 Service/DAO,再把结果forward到 JSP 渲染。这一步能让你理解请求分发的核心逻辑。

第二件:引入 JSTL 和 EL,把你页面里的<% %>脚本全部替换掉。替换完成之后你会发现,JSP 页面从"带 Java 代码的 HTML"变成了"带标签的 HTML",可读性提升一大截。

第三件:把 JDBC 换成连接池,加上 Filter 处理统一编码和登录拦截。这两个东西一加,项目的工程化程度就完全不一样了。

我个人带了这么多年新人,越来越觉得 JSP 就像学车时候的手动挡教练车——你以后可能开自动挡,但手动挡教会你的离合、换挡、半联动原理,会让你在遇到任何变速箱问题时都不慌。技术会迭代,但底层机制不会变。当你某天被一个老系统逼着去work/Catalina/localhost/项目名/org/apache/jsp/目录翻编译文件时,你会感谢今天认真看完编译机制的那个自己。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询