☰
Servlet+JSP学生管理系统实战:从404/500错误到Tomcat底层原理
2026/10/1 22:29:51 网站建设 项目流程

1. 这不是又一个“Hello World”:为什么今天还要手写Servlet+JSP学生管理系统?

你点开这个标题,大概率是正在赶Java Web课设、毕业设计,或者被公司老项目临时拉去维护一段十年前的代码。别急着关页面——我带过三届计算机专业实训,每年都有至少20个学生在“基于Servlet、JSP的学生管理系统”这个题目上卡在同一个地方:不是不会写增删改查,而是写完发现页面乱码、表单提交404、跳转后session丢失、JSP里EL表达式不生效……最后交上去的系统,首页能显示,但点击“添加学生”就报500,后台日志里全是java.lang.ClassNotFoundException: org.apache.jsp.index_jsp这种让人头皮发麻的错误。

这恰恰说明,Servlet+JSP这套技术栈远没有过时,它是一把“解剖刀”,能让你真正看清Web应用底层运行逻辑。你看那些Spring Boot项目,自动配置再智能,一旦Tomcat启动失败、Filter链执行异常、JSP编译出错,最终还是要回到web.xml怎么配、servlet-mapping路径是否正确、jsp-config里page-encoding设没设这些基础问题上。我去年帮一家做教务系统的公司排查线上故障,问题根源就是他们用Maven打包时漏掉了/WEB-INF/lib下的jasper.jar,导致JSP无法编译——而这个jar包,在Eclipse里新建Dynamic Web Project时是默认勾选的,但在IntelliJ IDEA里却需要手动添加。

所以这个项目的价值,从来不是“做个能用的系统”,而是建立一套完整的、可验证的Web容器认知模型:HTTP请求如何被Servlet接收、如何解析参数、如何转发给JSP渲染、JSP又如何被Tomcat编译成Servlet类、最终字节码如何被JVM加载执行。整个链条里,任何一个环节出错,都会表现为前端一个看似简单的404或500。而源码的价值,就在于它把所有隐含的约定和默认行为都摊开给你看——比如为什么<%@ page contentType="text/html;charset=UTF-8" %>必须写在JSP第一行?因为Tomcat的Jasper引擎在解析JSP时,会从头扫描直到遇到第一个page指令,再据此设置响应头和字符集;如果写在HTML标签后面,前面的空白字符就会被当成静态文本输出,导致Content-Type头已发送,再设置就无效了。

你不需要记住所有API,但必须理解:HttpServletRequest里的getParameter()和getAttribute()根本不是一回事;RequestDispatcher.forward()是服务器端跳转,URL不变,而response.sendRedirect()是客户端重定向,URL会变;session.setAttribute()存的是对象引用,不是深拷贝,所以千万别把ArrayList直接塞进去然后在另一个Servlet里add()——我见过太多人因此出现并发修改异常。这些细节,不是文档里一句话能讲清的,得在真实调试中踩坑、看日志、打断点,才能刻进肌肉记忆。

2. 架构设计与技术选型:为什么坚持不用Spring Boot?

很多人看到标题第一反应是:“都2024年了还写Servlet?用Spring Boot三行代码搞定的事,干嘛自找麻烦?”这个问题问得特别好,答案也很实在:因为Spring Boot帮你屏蔽了太多东西,反而让你看不见Web容器的本质。就像学开车,先上自动驾驶的特斯拉,你永远不知道油门、离合、档位之间的配合逻辑。而Servlet+JSP,就是那台化油器的老捷达——零件全露在外面,哪个螺丝松了、哪根管路堵了,你一眼就能看见。

我们这个学生管理系统的架构,严格遵循Java EE规范的分层思想,但刻意去掉所有框架封装:

  • 表现层(Presentation Layer):纯JSP + JSTL + EL表达式。不写一行JavaScript操作DOM,所有交互靠表单提交和超链接。这样做的目的,是强制你理解HTTP的无状态本质——每次请求都是全新的HttpServletRequest对象,数据要靠request.setAttribute()传给JSP,或者靠session跨请求保持。

  • 控制层(Controller Layer):标准HttpServlet子类。每个功能对应一个Servlet,比如StudentAddServlet处理添加、StudentListServlet处理列表查询。这里的关键是web.xml中的映射配置,它定义了URL路径到Servlet类的绑定关系。很多初学者以为注解@WebServlet("/add")更方便,但实际项目中,web.xml的集中配置更利于运维人员快速定位路由规则,也避免了IDE在热部署时因注解扫描失败导致的404。

  • 业务层(Business Layer):StudentService接口及其实现类。这里不做任何Spring的依赖注入,而是用最原始的new StudentServiceImpl()方式获取实例。目的是让你看清:所谓“服务层”,本质就是一堆方法调用,它的职责是协调DAO和处理业务规则,而不是被框架管理的对象。

  • 数据访问层(Data Access Layer):StudentDAO接口 +JdbcStudentDAO实现。用原生JDBC,手动写PreparedStatement,手动处理ResultSet,手动关闭Connection、Statement、ResultSet。虽然繁琐,但当你亲手写try...catch...finally释放资源时,才会真正理解连接池的意义——为什么Druid要配置maxActive=20?因为你的JdbcStudentDAO里每执行一次查询,就打开一个物理连接,不回收就会耗尽数据库连接数。

  • 实体层(Entity Layer):StudentPOJO类,只有字段、getter/setter、无参构造器。不加Lombok,不加Jackson注解,纯粹的Java Bean。这样设计,是为了让JSP里的<c:forEach items="${studentList}" var="stu">能直接通过反射调用getStuName()获取属性值——JSTL的EL表达式底层就是PropertyUtils.getProperty(),它要求属性名必须严格匹配getter方法名。

整个架构没有MVC框架的自动绑定、没有ORM的懒加载、没有AOP的事务代理。所有数据流转,都靠你手动request.setAttribute("studentList", list)、手动session.setAttribute("currentUser", user)、手动response.sendRedirect("list.jsp")。这种“笨办法”,恰恰是最高效的入门路径。我带过的学员里,凡是能把这套流程完整跑通的,后续学Spring MVC时,对@ModelAttribute、ModelAndView、RedirectAttributes的理解速度,比别人快一倍不止——因为他们已经亲手做过这些事,只是Spring把它自动化了而已。

3. 核心细节解析:从web.xml到JSP编译,每一个环节都不能错

3.1 web.xml:那个被时代遗忘却至关重要的配置中心

很多人以为web.xml是过时的古董,其实它是整个Web应用的“宪法”。Tomcat启动时,第一件事就是读取/WEB-INF/web.xml,根据里面的配置初始化Servlet、Filter、Listener,并建立URL映射关系。我们项目的web.xml核心配置如下:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 应用名称 --> <display-name>StudentManagementSystem</display-name> <!-- 全局上下文参数:指定数据库驱动 --> <context-param> <param-name>driverClassName</param-name> <param-value>com.mysql.cj.jdbc.Driver</param-value> </context-param> <!-- 全局初始化参数:数据库连接URL --> <context-param> <param-name>jdbcUrl</param-name> <param-value>jdbc:mysql://localhost:3306/student_db?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8</param-value> </context-param> <!-- Servlet定义 --> <servlet> <servlet-name>StudentListServlet</servlet-name> <servlet-class>com.example.servlet.StudentListServlet</servlet-class> <!-- 初始化参数:指定每页显示条数 --> <init-param> <param-name>pageSize</param-name> <param-value>10</param-value> </init-param> </servlet> <!-- Servlet映射 --> <servlet-mapping> <servlet-name>StudentListServlet</servlet-name> <url-pattern>/list</url-pattern> </servlet-mapping> <!-- 默认欢迎文件 --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <!-- 错误页面配置 --> <error-page> <error-code>404</error-code> <location>/error/404.jsp</location> </error-page> <error-page> <error-code>500</error-code> <location>/error/500.jsp</location> </error-page> </web-app>

关键点解析:

  • context-param定义的是全局参数,所有Servlet都能通过getServletContext().getInitParameter("driverClassName")获取,适合配置数据库连接信息这类应用级常量。
  • init-param定义的是Servlet级参数,只能在对应Servlet内通过getInitParameter("pageSize")获取,适合配置该Servlet特有的行为参数。
  • <url-pattern>/list</url-pattern>中的斜杠至关重要:/list表示精确匹配,/list/*表示路径匹配,*.do表示扩展名匹配。如果写成list(没斜杠),Tomcat会认为这是相对于当前目录的相对路径,导致404。
  • welcome-file-list指定了根路径/访问时自动加载的页面,但注意:index.jsp必须放在Web应用根目录下,不能放在/WEB-INF/里(因为/WEB-INF/是受保护目录,外部无法直接访问)。

提示:IntelliJ IDEA创建Web项目时,默认不生成web.xml,需要手动在src/main/webapp/WEB-INF/下创建。而Eclipse的Dynamic Web Project向导会自动生成,但版本可能为2.5,需手动升级到4.0以支持<jsp-config>等新特性。

3.2 JSP页面编码与EL表达式失效的真相

JSP页面乱码、EL表达式不生效,是新手最高频的两个问题。它们的根源,都藏在JSP编译过程里。

首先看编码问题。一个典型的JSP页面开头应该是:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>学生列表</title> </head> <body> <h1>学生信息</h1> <c:forEach items="${studentList}" var="stu"> <p>姓名:${stu.stuName},学号:${stu.stuId}</p> </c:forEach> </body> </html>

这里三个编码声明缺一不可:

  • contentType="text/html; charset=UTF-8":告诉浏览器,这个响应体是UTF-8编码的HTML。
  • pageEncoding="UTF-8":告诉JSP引擎,这个.jsp文件本身是UTF-8编码保存的,用于正确读取文件内容。
  • <meta charset="UTF-8">:作为HTML文档的元信息,是浏览器解析HTML时的后备编码声明。

三者必须一致。如果.jsp文件用GBK保存,但pageEncoding写UTF-8,JSP引擎读取时就会乱码;如果contentType写ISO-8859-1,浏览器就会用Latin1解码,中文全变成方块。

再来看EL表达式失效。${studentList}不显示数据,常见原因有三个:

  1. JSP版本太低:JSP 2.0以下默认禁用EL。解决方案是在page指令中显式开启:<%@ page isELIgnored="false" %>。但更根本的解决是确保web.xml声明的是Servlet 3.0+规范(即version="3.0"或更高),因为JSP 2.1+默认启用EL。
  2. 作用域错误:studentList是存在request作用域还是session作用域?JSP里${studentList}默认从page→request→session→application顺序查找。如果Servlet里写的是session.setAttribute("studentList", list),那么JSP里必须写${sessionScope.studentList}。
  3. JSTL标签库未正确引入:<c:forEach>需要JSTL支持。必须在web.xml中配置<taglib>,或在JSP顶部添加:
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    并且/WEB-INF/lib/下必须有jstl.jar和standard.jar(JSP 1.2)或jakarta-taglibs-standard-1.1.2.jar(JSP 2.0+)。IntelliJ IDEA中,右键项目→Add Framework Support→选择Java EE Web Module,会自动添加JSTL库。

注意:Eclipse中创建JSP文件时,如果提示“JSP is not supported in this project”,说明项目Facet没配置Web Module。右键项目→Properties→Project Facets→勾选Dynamic Web Module并设置版本(推荐4.0)。

3.3 数据库连接与JDBC资源管理的生死线

学生管理系统离不开数据库。我们用MySQL 8.0,建表语句如下:

CREATE DATABASE student_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_db; CREATE TABLE student ( stu_id VARCHAR(12) PRIMARY KEY, stu_name VARCHAR(20) NOT NULL, stu_age INT, stu_gender ENUM('男','女'), stu_class VARCHAR(20), stu_phone VARCHAR(15), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO student VALUES ('2021001', '张三', 20, '男', '计算机1班', '13800138000', NOW()), ('2021002', '李四', 19, '女', '软件工程2班', '13900139000', NOW());

关键点在于JDBC连接管理。JdbcStudentDAO的getConnection()方法必须这样写:

private Connection getConnection() throws SQLException { String driver = getServletContext().getInitParameter("driverClassName"); String url = getServletContext().getInitParameter("jdbcUrl"); String username = "root"; String password = "123456"; try { Class.forName(driver); // 加载驱动(MySQL 8.0+可省略,但保留更兼容) return DriverManager.getConnection(url, username, password); } catch (ClassNotFoundException e) { throw new RuntimeException("JDBC Driver not found", e); } }

而查询方法必须严格遵循资源释放规范:

public List<Student> findAll() { List<Student> list = new ArrayList<>(); String sql = "SELECT * FROM student ORDER BY create_time DESC"; Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = getConnection(); ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()) { Student stu = new Student(); stu.setStuId(rs.getString("stu_id")); stu.setStuName(rs.getString("stu_name")); stu.setStuAge(rs.getInt("stu_age")); stu.setStuGender(rs.getString("stu_gender")); stu.setStuClass(rs.getString("stu_class")); stu.setStuPhone(rs.getString("stu_phone")); list.add(stu); } } catch (SQLException e) { e.printStackTrace(); // 实际项目应记录日志 } finally { // 必须按rs→ps→conn顺序关闭! if (rs != null) try { rs.close(); } catch (SQLException e) {} if (ps != null) try { ps.close(); } catch (SQLException e) {} if (conn != null) try { conn.close(); } catch (SQLException e) {} } return list; }

为什么关闭顺序必须是rs→ps→conn?因为ResultSet依赖Statement,Statement依赖Connection。如果先关conn,rs就变成无效游标,调用rs.next()会抛SQLException。而try-with-resources语法(Java 7+)可以自动处理,但为了教学清晰,我们坚持手动关闭。

实操心得:本地测试时,建议在MySQL配置文件my.cnf中添加wait_timeout=28800(8小时),避免连接空闲超时断开。生产环境必须用连接池(如Druid),但学习阶段,亲手写conn.close()比配置Druid的maxWait参数更能理解连接复用的价值。

4. 完整实操流程:从Eclipse创建项目到Tomcat部署上线

4.1 Eclipse创建基于Maven的Servlet项目(最稳妥的起点)

虽然标题说“附完整源码”,但源码的价值在于可复现。下面是从零开始的详细步骤,确保你在任何一台装了JDK 8+和Eclipse的电脑上,都能10分钟内跑起来。

第一步:创建Maven Web项目

  1. Eclipse菜单栏:File → New → Other → Maven → Maven Project
  2. 勾选“Create a simple project(skip archetype selection)”,Next
  3. Group Id填com.example,Artifact Id填student-management-system,Version用默认0.0.1-SNAPSHOT,Finish
  4. 此时项目结构是标准Maven布局,但缺少Web目录。右键项目→Properties→Project Facets→勾选“Dynamic Web Module”,Version选“4.0”,同时勾选“Java”和“JavaScript”。
  5. 点击“Further configuration available...”,在“Content directory”中填src/main/webapp,点击OK。Eclipse会自动创建src/main/webapp/WEB-INF/web.xml。

第二步:配置Maven依赖(pom.xml)在pom.xml的<dependencies>中添加:

<!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP API --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- MySQL JDBC Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>

注意<scope>provided</scope>表示这些jar由Tomcat提供,打包时不包含,避免冲突。

第三步:创建目录结构与核心文件按以下路径创建文件:

  • src/main/java/com/example/servlet/StudentListServlet.java
  • src/main/java/com/example/dao/StudentDAO.java
  • src/main/java/com/example/dao/JdbcStudentDAO.java
  • src/main/java/com/example/entity/Student.java
  • src/main/webapp/index.jsp
  • src/main/webapp/list.jsp
  • src/main/webapp/add.jsp
  • src/main/webapp/WEB-INF/web.xml

第四步:编写StudentListServlet(核心控制器)

package com.example.servlet; import com.example.dao.StudentDAO; import com.example.dao.JdbcStudentDAO; import com.example.entity.Student; 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 StudentListServlet extends HttpServlet { private StudentDAO studentDAO; @Override public void init() throws ServletException { // 初始化DAO实例 this.studentDAO = new JdbcStudentDAO(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 查询所有学生 List<Student> studentList = studentDAO.findAll(); // 将数据存入request作用域 request.setAttribute("studentList", studentList); // 转发到list.jsp request.getRequestDispatcher("/list.jsp").forward(request, response); } }

第五步:配置Tomcat服务器

  1. Eclipse菜单栏:Window → Show View → Servers
  2. 点击“No servers are available. Click this link to create a new server.”
  3. 选择Apache → Tomcat v9.0 Server,Next
  4. Browse选择Tomcat安装目录(如D:\apache-tomcat-9.0.83),Finish
  5. 右键Servers视图中的Tomcat → Add Deployment → 选择你的项目,Finish
  6. 右键Tomcat → Start,控制台输出INFO: Server startup in [xxx] milliseconds即启动成功。

第六步:访问测试浏览器打开http://localhost:8080/student-management-system/list,如果看到学生列表,说明Servlet、DAO、JSP全部打通。此时,你可以用Chrome开发者工具的Network标签,查看HTTP请求的Status Code、Response Headers(确认Content-Type: text/html;charset=UTF-8)、Response Body(确认HTML结构正确)。

实操心得:如果访问/list报404,先检查Eclipse控制台是否有SEVERE: Error starting static Resources错误,这通常意味着web.xml路径不对或Tomcat没识别到Web应用;如果报500,看控制台堆栈,90%是ClassNotFoundException(缺jar包)或SQLException(数据库连不上)。我的经验是,遇到500先看最后一行Caused by:,那里才是真正的错误源头。

4.2 IntelliJ IDEA中创建JSP项目(避坑指南)

IntelliJ IDEA对JSP的支持不如Eclipse成熟,新手容易掉坑。以下是关键配置步骤:

第一步:创建Web Application项目

  1. File → New → Project → Java Enterprise → Web Application(不要选Maven,先保证基础功能)
  2. 勾选“Create web.xml”和“Add sample code”,Next
  3. 项目名填student-management-idea,Finish

第二步:解决JSP语法高亮和Color Scheme问题

  • 问题:IDEA中JSP文件没有语法高亮,编辑器主题里找不到JSP相关设置。
  • 解决:File → Settings → Editor → Color Scheme → General → Syntax → JSP,勾选“Enable highlighting for JSP files”。如果列表里没有JSP,说明插件未启用:Settings → Plugins → 搜索“JSP Support”,确保已启用。

第三步:手动添加JSTL库

  • IDEA不会自动添加JSTL。下载jstl-1.2.jar和standard-1.1.2.jar,放到/WEB-INF/lib/下。
  • 右键lib文件夹 → Add as Library,确保在Project Structure → Libraries中能看到这两个jar。

第四步:配置Tomcat并部署

  • Run → Edit Configurations → + → Tomcat Server → Local
  • 在Deployment标签页,点击+ → Artifact → 选择你的项目名称:war exploded
  • Application context填/student-idea(避免根路径冲突)
  • 启动后访问http://localhost:8080/student-idea/list

注意:IDEA中JSP编译后的Java类默认在out/artifacts/xxx_war_exploded/下的org/apache/jsp/目录。如果你想查看list_jsp.java源码,就在该路径下找。这比Eclipse的work/Catalina/localhost/xxx/org/apache/jsp/更直观,也印证了JSP本质就是Servlet。

5. 常见问题与排查技巧实录:那些让我熬夜到凌晨三点的Bug

5.1 “HTTP Status 404 – The requested resource [ /xxx ] is not available” —— URL映射之谜

这是最让人抓狂的错误。表面看是路径不对,但背后有五种完全不同的原因:

现象可能原因排查命令/方法解决方案
访问/list报404,但/index.jsp正常web.xml中<servlet-mapping>的<url-pattern>写错查看Tomcat启动日志,搜索Mapping servlet确保<url-pattern>/list</url-pattern>,不是list或/list/
访问/list报404,且Tomcat日志无任何映射信息web.xml未被Tomcat加载检查/WEB-INF/web.xml文件是否存在,XML格式是否正确(用在线XML校验工具)重新创建web.xml,确保根元素是<web-app>,namespace正确
访问/list报404,日志显示Mapping servlet StudentListServlet to [/list]Servlet类路径错误在web.xml中检查<servlet-class>是否为完整包名,如com.example.servlet.StudentListServlet确认.class文件在WEB-INF/classes/com/example/servlet/下
访问/list报404,但/servlet/StudentListServlet能访问使用了@WebServlet注解,但web.xml中<web-app>版本低于3.0查看web.xml的version属性升级web.xml到version="3.0"或更高
访问/list报404,且项目在Servers视图中显示为“[Stopped]”Tomcat未成功部署该项目右键Servers中的Tomcat → Clean…,然后重启清理Tomcat工作目录work/Catalina/localhost/

独家技巧:在StudentListServlet的doGet()方法第一行加System.out.println("StudentListServlet executed!");,如果控制台没输出,说明请求根本没到达Servlet,问题一定在映射层;如果输出了,但页面还是404,说明是转发或重定向的问题。

5.2 “HTTP Status 500 – java.lang.NullPointerException” —— 空指针的七种死法

NPE是Java Web开发的“家常便饭”,但在Servlet+JSP场景下,它往往指向特定的资源管理漏洞:

  1. DAO未初始化:studentDAO.findAll()前没调用init()方法,studentDAO为null。

    • 解决:在Servlet的init()方法中初始化DAO,或在doGet()中判断if (studentDAO == null) studentDAO = new JdbcStudentDAO();
  2. 数据库连接失败:DriverManager.getConnection()返回null,但代码没判空。

    • 解决:JDBC规范规定,连接失败时抛SQLException,不是返回null。所以NPE通常是因为conn对象在try块外声明,catch中没赋值,导致finally里conn.close()时为null。
  3. JSP中EL表达式取值为空:${stu.stuName}中stu为null,调用getStuName()时NPE。

    • 解决:用EL的空安全操作符:${stu.stuName != null ? stu.stuName : '未知'},或在JSTL中用<c:if test="${not empty stu}">
  4. request.getAttribute()返回null:Servlet里没setAttribute(),JSP里直接${studentList}。

    • 解决:在JSP顶部加<c:if test="${empty studentList}">暂无数据</c:if>,避免遍历null集合。
  5. web.xml参数读取失败:getServletContext().getInitParameter("jdbcUrl")返回null,后续拼接字符串时报NPE。

    • 解决:在getConnection()方法开头加if (url == null) throw new RuntimeException("jdbcUrl not configured in web.xml");
  6. ResultSet未判空:rs.getString("stu_name")返回null,但代码直接赋值给非null字段。

    • 解决:数据库字段设为NOT NULL,或Java代码中用StringUtils.defaultString(rs.getString("stu_name"))
  7. IDE缓存导致class文件未更新:修改了Servlet代码,但Tomcat运行的还是旧class。

    • 解决:Eclipse中Project → Clean;IDEA中Build → Clean and rebuild;Tomcat中右键→Clean…

实操心得:我有个铁律——只要出现NPE,第一反应不是看堆栈,而是打开Tomcat的logs/catalina.out,搜索Caused by:。90%的NPE前面都有更早的SQLException或ClassNotFoundException,那是真正的病因。比如Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,说明MySQL驱动jar没放对位置,这时候修NPE代码毫无意义。

5.3 JSP页面中文乱码终极解决方案

乱码问题本质是字符集在传输链路上的不一致。我们梳理完整链条:

浏览器输入URL → HTTP请求(GET/POST)→ Tomcat接收 → Servlet处理 → JSP渲染 → HTTP响应 → 浏览器显示

每个环节都可能出错:

  • GET请求中文乱码:浏览器将中文参数用UTF-8编码,Tomcat默认用ISO-8859-1解码,结果乱码。

    • 解决:在web.xml中配置<filter>,或在Servlet中手动转码:new String(request.getParameter("name").getBytes("ISO-8859-1"), "UTF-8")
  • POST请求中文乱码:必须在request.getParameter()之前调用request.setCharacterEncoding("UTF-8")。

    • 解决:写一个CharacterEncodingFilter,在doFilter()中执行request.setCharacterEncoding("UTF-8"),并配置到web.xml。
  • JSP页面乱码:如前所述,pageEncoding、contentType、<meta>三者不一致。

    • 解决:统一设为UTF-8,并确保.jsp文件本身用UTF-8保存(Eclipse中右键文件→Properties→Resource→Text file encoding)。
  • 数据库存储乱码:MySQL连接URL没加characterEncoding=utf8,或数据库、表、字段的collation不是utf8mb4_unicode_ci。

    • 解决:执行SHOW VARIABLES LIKE 'character_set_%';,确认character_set_client、character_set_connection、character_set_results均为utf8mb4。
  • Tomcat控制台乱码:Windows CMD默认GBK,Tomcat日志输出中文变乱码。

    • 解决:修改bin/catalina.bat,在set JAVA_OPTS=行后添加-Dfile.encoding=UTF-8。

终极验证法:在Servlet中写:

System.out.println("中文测试:" + request.getParameter("name")); // 控制台输出 request.setAttribute("testStr", "中文测试"); request.getRequestDispatcher("/test.jsp").forward(request, response);

如果控制台显示正常,但JSP页面乱码,问题一定在JSP编码;如果控制台就乱码,问题在请求编码。

5.4 “The requested resource is not available” 与 “HTTP Status 404” 的本质区别

很多人混淆这两个错误,其实它们代表完全不同的故障层级:

  • “The requested resource is not available”:这是Tomcat的默认错误页面文字,当发生404时,Tomcat会返回这个HTML页面。它本身不是错误码,而是404的友好提示。

  • “HTTP Status 404”:这是HTTP协议规定的状态码,表示服务器找不到请求的资源。它是一个数字,不是文字。

真正要关注的是HTTP状态码。用Chrome开发者工具的Network标签,点击请求,看Headers → Response Headers → Status,如果是404 Not Found,说明资源确实不存在;如果是500 Internal Server Error,说明服务器内部出错。

最后分享一个小技巧:在web.xml中配置自定义错误页面后,Tomcat就不会显示“The requested resource is not available”了,而是显示你写的404.jsp。这不仅是美化,更是调试利器——你可以在404.jsp里打印request.getRequestURL()和request.getQueryString(),实时看到用户到底访问了什么URL,比翻日志快十倍。

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

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

立即咨询