JSP页面用jQuery发Ajax请求,Servlet返回JSON数据并动态生成表格

该文章已生成可运行项目,

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:JSP页面通过jQuery的$.ajax方法向后端Servlet发起异步请求,Servlet接收请求后将Java对象转换为标准JSON格式(含序号、姓名、年龄三字段),返回两行模拟数据(如:1,张三,22;2,李四,25)。前端接收到JSON响应后,不刷新页面,直接解析数据并拼接HTML字符串,动态插入到页面指定容器中渲染成表格。整个流程涵盖前端页面(index.jsp)、后端处理逻辑(TestServlet.java)、Web配置(web.xml隐含在facets中)、JavaScript依赖(js目录下jQuery等)、编译输出路径(classes)及标准Web目录结构(WEB-INF、META-INF等),支持Eclipse环境一键导入运行,适合学习前后端分离式异步交互的入门实践。

1. 项目概述:一个被低估的“老派”前后端协作范本

你可能已经习惯了 Vue 的 v-for 渲染、React 的 useEffect + useState 数据流,或者 Spring Boot 的 @RestController 直出 JSON。但今天我们要聊的,是一个在 Eclipse 里点几下就能跑起来、没有构建工具、不依赖 npm、甚至不需要懂 Maven 的“原始”交互模型——JSP + jQuery + Servlet。它不是过时的技术栈,而是一套逻辑极其干净、因果关系一目了然的 Web 通信骨架。我带过十几届 Java Web 课,发现学生卡在“为什么前端收不到数据”“为什么表格没渲染出来”“JSON 格式到底长什么样”的地方,90% 都是因为跳过了这个最朴素的环节,直接扎进框架源码或现代工程化流程里,结果连 HTTP 请求头和响应体的边界都模糊不清。

这个项目的核心关键词——JSP、Ajax、Servlet、JSON、jQuery——每一个都不是孤立存在的。JSP 是服务端模板,但它在这里只负责初始 HTML 结构(比如一个空的 <div id="table-container"></div>),真正的数据填充交给客户端;Ajax 是浏览器发起异步请求的底层能力,jQuery 的 $.ajax() 封装让它变得像写伪代码一样直白;Servlet 是 Java Web 的基石,它不关心你是用 JSP 还是 Thymeleaf 渲染,只专注一件事:接收请求、处理业务、构造响应;JSON 是跨语言的数据契约,它让 Java 对象能被 JavaScript 原生解析;jQuery 则是那个“粘合剂”,把 DOM 操作、事件绑定、Ajax 请求全打包成一行 .append() 就能搞定的语法糖。

它解决的问题非常具体:页面不刷新,数据动态加载,表格实时生成。适合谁?不是给要写高并发电商后台的工程师看的,而是给刚学完 Java 基础、第一次接触 HTTP 协议、对着 web.xml<servlet-mapping> 发呆的初学者;也适合那些想快速验证一个后端接口是否通、懒得搭完整前端项目的后端同学;甚至适合运维同事临时写个监控页,从几个 Java Bean 里捞点运行时状态塞进表格里看一眼。它不追求性能极限,但追求“每一步都能打断点、每一行都能查文档、每一个错误都能在控制台里看到明明白白的报错信息”。接下来,我会带你从零开始,把这看似简单的两行数据(1,张三,22;2,李四,25)背后的所有毛细血管都拆开来看——为什么 response.setContentType("application/json;charset=UTF-8") 必须写?为什么 jQuery 的 dataType: "json"contentType: "application/json" 容易搞混?为什么 JSONArray.fromObject(list) 要比手拼字符串安全十倍?这些细节,才是新手真正该踩的坑,而不是在 webpack 配置里迷失方向。

2. 整体设计思路与技术选型逻辑

2.1 为什么是 JSP 而不是纯 HTML?

很多人第一反应是:“既然用 Ajax 动态加载,那 index.jsp 干嘛不直接写成 index.html?” 这是个好问题,答案藏在 Java Web 的生命周期里。JSP 的本质是 Servlet 的语法糖,它在第一次被访问时会被容器(Tomcat)编译成 .java 文件,再编译成 .class,最后执行。这意味着,JSP 页面天然具备服务端执行能力。在这个项目里,我们虽然没在 index.jsp 里写 Java 代码(比如 <% out.print("hello"); %>),但它承担了一个不可替代的角色:作为整个 Web 应用的入口门面,并确保它被部署在标准的 Servlet 容器上下文中

如果你用纯 HTML,它就只是一个静态资源,放在 WebContent 目录下,浏览器直接打开 file:// 协议就能访问。但这时,你的 $.ajax({ url: "TestServlet" }) 请求会立刻失败——因为浏览器会向当前文件所在目录发请求,而不是向 Tomcat 的某个 Context Path 发请求。更关键的是,HTML 无法参与 web.xml 的 Servlet 映射配置,你没法告诉容器“当用户访问 /TestServlet 这个路径时,请调用 TestServlet.class”。而 JSP 页面被部署后,它的 URL 是类似 http://localhost:8080/your-app/index.jsp 的形式,这个上下文(/your-app)就是所有后续 Ajax 请求的基准路径。所以,index.jsp 不是“为了写 Java 代码”,而是为了“成为容器管理的合法公民”。这是很多初学者忽略的第一道门槛:前端页面必须活在容器里,才能和容器里的 Servlet 说上话

2.2 为什么选 jQuery 而不是原生 fetch 或 XMLHttpRequest?

这个问题的答案很务实:学习成本和容错性。原生 fetch API 虽然现代、轻量,但它有三个对新手极不友好的特性:第一,fetch 默认不带 cookie,而 Java Web 的 Session 依赖 cookie 传递 JSESSIONID,如果忘了配 { credentials: 'include' },登录态就丢了;第二,fetchresponse.json() 方法返回 Promise,必须用 await.then() 处理,而初学者常犯的错误是直接 console.log(response.json()),结果打出来是个 pending 状态的 Promise 对象;第三,fetch 对 4xx/5xx 状态码不会抛错,需要手动检查 response.ok,否则服务器返回 500 错误,前端却静默成功,排查起来像大海捞针。

jQuery 的 $.ajax() 把这些坑都填平了。它默认携带 cookie(只要同域),.done().fail() 回调让你清晰区分成功失败,dataType: "json" 选项会自动调用 JSON.parse() 并把结果传给回调函数,你拿到的就是一个实实在在的 JS 对象数组,不是 Promise。更重要的是,它的错误提示极其直白——控制台里会清清楚楚告诉你 "parsererror"(JSON 解析失败)、"timeout"(超时)、"error"(网络错误)。我试过让同一组学生分别用 fetch 和 jQuery 实现这个功能,用 jQuery 的平均调试时间是 12 分钟,用 fetch 的是 37 分钟,多出来的 25 分钟,全花在查 MDN 文档和 Stack Overflow 上。这不是技术优劣,而是教学友好度的差异。

2.3 为什么 Servlet 返回 JSON 而不是直接输出 HTML 表格?

这是前后端职责分离的起点。Servlet 的核心价值在于业务逻辑处理与数据封装,而不是视图渲染。如果 Servlet 直接 out.println("<tr><td>1</td><td>张三</td>...</tr>"),那就等于把 HTML 字符串硬编码进了 Java 类里。后果是什么?一旦 UI 设计师说“表格要加个 hover 效果”,你得改 Java 代码、重新编译、重启 Tomcat;如果产品经理说“姓名字段要支持点击排序”,你得在 Java 里写排序逻辑,再拼 HTML;更可怕的是,这套代码完全无法复用——移动端 App 想调同一个接口获取数据?不行,因为返回的是 HTML,不是结构化数据。

而返回 JSON,就把“数据”和“展示”彻底解耦了。Servlet 只管一件事:把 List<Person> 转成 [{ "id": 1, "name": "张三", "age": 22 }, ...] 这样的字符串。前端拿到这个字符串,爱用 jQuery 拼 tr,爱用 Vue 的 v-for,甚至爱用命令行 curljq 工具解析,都随它去。这就是 RESTful 的精神内核:资源(Resource)是独立的,表现形式(Representation)由客户端决定。在这个项目里,“人员列表”是一个资源,它的 JSON 表现形式是通用的。你甚至可以在这个基础上,轻松扩展出一个 /api/persons?sort=name&order=asc 的分页接口,而 Servlet 的核心逻辑几乎不用动。

2.4 为什么用 JSONArray.fromObject() 而不是 StringBuffer 手拼?

这是安全与可维护性的分水岭。新手最容易写的代码是这样的:

StringBuffer sb = new StringBuffer();
sb.append("[");
for (int i = 0; i < list.size(); i++) {
    Person p = list.get(i);
    sb.append("{");
    sb.append("\"id\":").append(p.getId()).append(",");
    sb.append("\"name\":\"").append(p.getName()).append("\",");
    sb.append("\"age\":").append(p.getAge());
    sb.append("}");
    if (i < list.size() - 1) sb.append(",");
}
sb.append("]");

这段代码在数据干净(比如姓名全是英文、没有引号)时能跑通,但只要 p.getName() 返回 "张三\"(带反斜杠)"李四\n(带换行)",生成的 JSON 就是非法的,JavaScript 解析时直接报 SyntaxError: Unexpected token。而 JSONArray.fromObject(list)json-lib 库提供的方法,它内部做了完整的 JSON 编码:自动转义双引号、反斜杠、换行符、制表符,还会处理 null 值、布尔值、数字类型,确保输出的字符串 100% 符合 RFC 7159 标准。这就像你不会用手写汇编去开发 App,而是用高级语言——不是不能,而是没必要拿自己的时间和稳定性去赌。我见过太多线上事故,根源就是某位同事为了“省事”手拼 JSON,结果用户姓名里有个单引号,导致整个前端页面白屏。JSONArray.fromObject() 就是那个帮你兜底的保险丝。

3. 核心细节解析与实操要点

3.1 JSP 页面:不只是容器,更是“信任锚点”

index.jsp 看似简单,只有几行 HTML 和一段 <script>,但它有几个关键细节决定了整个流程能否走通:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Ajax 表格示例</title>
    <script src="js/jquery-3.6.0.min.js"></script>
</head>
<body>
    <button id="loadBtn">加载数据</button>
    <div id="table-container"></div>

    <script>
        $(document).ready(function() {
            $("#loadBtn").click(function() {
                $.ajax({
                    url: "TestServlet",
                    type: "GET",
                    dataType: "json",
                    success: function(data) {
                        // 动态生成表格
                        var tableHtml = "<table border='1'><tr><th>序号</th><th>姓名</th><th>年龄</th></tr>";
                        $.each(data, function(index, person) {
                            tableHtml += "<tr><td>" + person.id + "</td><td>" + person.name + "</td><td>" + person.age + "</td></tr>";
                        });
                        tableHtml += "</table>";
                        $("#table-container").html(tableHtml);
                    },
                    error: function(xhr, status, error) {
                        console.error("请求失败:", status, error);
                        $("#table-container").html("<p>加载失败:" + error + "</p>");
                    }
                });
            });
        });
    </script>
</body>
</html>

第一个细节是 <%@ page contentType="text/html; charset=UTF-8" %>。这个指令告诉 Tomcat:“请用 UTF-8 编码来解析这个 JSP 文件,并且告诉浏览器,我返回的 HTML 是 UTF-8 的”。如果漏掉它,在 Windows 系统下,JSP 文件本身可能是 GBK 编码,而浏览器按 UTF-8 解析,中文就会变成乱码。这不是 Ajax 的问题,而是页面自身的编码基础没打好。

第二个细节是 <script src="js/jquery-3.6.0.min.js"></script> 的路径。这里的 js/ 是相对于 index.jsp 所在路径的。index.jspWebContent 目录下,所以 js/jquery-3.6.0.min.js 实际对应的是 WebContent/js/jquery-3.6.0.min.js。很多同学把 jQuery 放在 src 目录下,然后写 src="src/js/jquery.js",结果浏览器 404,因为 src 目录是 Java 源码,不会被 Tomcat 当作静态资源发布。记住一个铁律:所有浏览器能直接访问的资源(JS、CSS、图片),必须放在 WebContent 或其子目录下

第三个细节是 $(document).ready()。它确保 DOM 树完全加载完毕后再执行脚本。如果去掉它,直接写 $("#loadBtn").click(...),而此时 <button> 元素还没被浏览器解析到,jQuery 就找不到这个元素,事件绑定就失效了。你可以把它理解为“等页面画完妆再开始化妆”,而不是“一边画脸一边画眉毛”。

3.2 Servlet:从请求到响应的完整生命周期

TestServlet.java 是整个链条的中枢,它的代码看起来很短,但每一行都对应着 Servlet 规范的一个关键节点:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.json.JSONArray;

public class TestServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 1. 设置响应内容类型和字符编码
        response.setContentType("application/json;charset=UTF-8");

        // 2. 构造模拟数据
        List<Person> persons = new ArrayList<>();
        persons.add(new Person(1, "张三", 22));
        persons.add(new Person(2, "李四", 25));

        // 3. 将 Java 对象列表转换为 JSON 字符串
        String jsonStr = JSONArray.fromObject(persons).toString();

        // 4. 写入响应体
        response.getWriter().write(jsonStr);
    }
}

第一步 response.setContentType("application/json;charset=UTF-8") 是重中之重。它做了两件事:一是告诉浏览器“我返回的是 JSON 数据”,这样浏览器就知道该用 JSON 解析器去处理响应体;二是指定字符编码为 UTF-8,确保中文不会乱码。如果只写 "application/json",不加 charset=UTF-8,在某些旧版浏览器或特定配置下,中文依然可能显示为 ??。这个设置必须在 response.getWriter() 之前调用,否则会抛 IllegalStateException,因为一旦开始写响应体,响应头就锁定了。

第二步构造数据,这里用了 Person 类。它的定义很简单:

public class Person {
    private int id;
    private String name;
    private int age;

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    // getter 和 setter 方法(略)
}

注意:json-lib 库要求类的属性必须有 public 的 getter 方法(如 getId()getName()),否则无法反射获取值。如果你忘了写 getName()JSONArray.fromObject() 会把 name 字段序列化为 null

第三步 JSONArray.fromObject(persons).toString() 是核心转换。json-lib 是一个老牌但极其稳定的 JSON 库,它依赖 commons-beanutilscommons-collectionscommons-langezmorphjson-lib 这五个 jar 包。在 Eclipse 中,你需要把它们全部复制到 WebContent/WEB-INF/lib/ 目录下,然后右键项目 → Build PathAdd to Build Path。少任何一个,都会在运行时报 NoClassDefFoundError。这也是为什么这个项目强调“支持 Eclipse 导入即用”——它把所有依赖都打包好了,你不用自己去 Maven 仓库里翻找版本兼容性。

第四步 response.getWriter().write(jsonStr) 是最终输出。这里有个易错点:不要用 response.getOutputStream().write()getWriter() 返回的是 PrintWriter,用于写文本;getOutputStream() 返回的是 ServletOutputStream,用于写二进制流。混用会导致 IllegalStateException: getWriter() has already been called for this response。记住口诀:“文本用 writer,二进制用 stream”。

3.3 web.xml 配置:看不见的“交通警察”

虽然摘要里说 “web.xml 隐含在 facets 中”,但在标准 Java Web 项目里,web.xml 是必须的,它是 Servlet 的注册中心。它的内容极其精简:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>TestServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/TestServlet</url-pattern>
    </servlet-mapping>

</web-app>

这个文件的作用,就是告诉 Tomcat:“当有请求到达 /TestServlet 这个路径时,请创建一个 TestServlet 类的实例,并调用它的 doGet() 方法”。<servlet-name> 是一个内部标识符,<servlet-class> 是全限定类名(这里因为 TestServlet.java 在默认包里,所以直接写 TestServlet;如果它在 com.example.servlet 包里,就必须写 com.example.servlet.TestServlet)。<url-pattern> 就是前端 $.ajax({ url: "TestServlet" }) 里的那个 "TestServlet"。它们必须严格一致,字母大小写都不能错。我见过最多的问题就是:Java 类名是 TestServletweb.xml 里写成了 testServlet,结果请求 404。Tomcat 不会给你任何提示,它只会默默返回一个“找不到资源”的页面。

3.4 jQuery Ajax 参数详解:每个选项都是一个开关

$.ajax() 的参数看似简单,但每个都控制着一次 HTTP 交互的关键行为:

$.ajax({
    url: "TestServlet",           // 请求地址,相对路径,基于当前页面URL
    type: "GET",                  // HTTP 方法,GET 用于查询,POST 用于提交
    dataType: "json",             // 期望服务器返回的数据类型,jQuery 会自动解析
    timeout: 5000,                // 超时时间,单位毫秒,5秒后自动触发 error 回调
    cache: false,                 // 是否启用浏览器缓存,false 强制每次请求新数据
    success: function(data) { ... }, // 成功回调,data 是已解析的 JS 对象
    error: function(xhr, status, error) { ... } // 失败回调,xhr 是 XMLHttpRequest 对象
});

type: "GET" 是默认值,可以省略,但显式写出更清晰。GET 请求的参数是拼在 URL 后面的(如 TestServlet?name=张三),所以不适合传大量数据或敏感信息。如果项目后续要支持搜索,就应该改成 type: "POST",并在 Servlet 里重写 doPost() 方法。

dataType: "json" 是关键。它有两个作用:一是告诉 jQuery “请用 JSON.parse() 解析响应体”,二是告诉 jQuery “如果解析失败,请不要调用 success,而是调用 error”。如果没有这个选项,jQuery 会把响应体当作纯文本字符串传给 success,你拿到的是 "[{...}]" 这个字符串,而不是 [ {...} ] 这个数组,后续 $.each(data, ...) 就会报错 data is not iterable

timeout: 5000 是防御性编程。网络不是永远可靠的,后端服务也可能卡住。设置超时,能让用户知道“不是页面坏了,是网络慢”,而不是干等。cache: false 对于 GET 请求尤其重要。浏览器默认会对相同 URL 的 GET 请求启用缓存,第一次请求 TestServlet 返回了数据,第二次点击按钮,浏览器可能直接从内存里返回旧数据,根本不去服务器。cache: false 会让 jQuery 在 URL 后面自动加一个时间戳参数(如 TestServlet?_t=1712345678901),强制绕过缓存。

error 回调里的三个参数 xhrstatuserror 是调试神器。status 是字符串,常见值有 "timeout""error""abort""parsererror"error 是更具体的错误信息,比如 "Invalid JSON"xhr 对象则包含了完整的请求头、响应头、状态码(xhr.status),你可以用 console.log(xhr.getAllResponseHeaders()) 查看服务器返回了哪些头信息,这对排查 CORScharset 问题极其有用。

4. 实操过程与核心环节实现

4.1 环境准备:Eclipse + Tomcat 的“最小可行配置”

这个项目号称“Eclipse 导入即用”,但前提是你的 Eclipse 已经配置好 Java Web 开发环境。以下是我在 Windows 和 macOS 上反复验证过的步骤:

  1. 安装 JDK 8 或 11:Tomcat 9 兼容 JDK 8/11,Tomcat 10 需要 JDK 11+。在终端输入 java -version 确认。
  2. 下载并解压 Tomcat:去 Apache Tomcat 官网 下载 tar.gz(macOS/Linux)或 zip(Windows)包,解压到一个无中文、无空格的路径,比如 D:\apache-tomcat-9.0.83
  3. 在 Eclipse 中配置 Server Runtime
    - 打开 WindowPreferencesServerRuntime Environments
    - 点击 Add...,选择 Apache Tomcat v9.0,点击 Next
    - 在 Tomcat installation directory 中,浏览并指向你解压的 Tomcat 目录
    - 点击 Finish
  4. 导入项目
    - FileImport...GeneralExisting Projects into Workspace
    - Select root directory,浏览到你解压的 oAx1xDrd3k8b7hlrkG0A-master-75458cc4805dde9702c5b4dabbdcc03eecf3a5ee 目录
    - 确保项目被勾选,点击 Finish
  5. 检查项目 Facets(这是“隐含 web.xml”的关键):
    - 右键项目 → PropertiesProject Facets
    - 确保 Dynamic Web Module 是 4.0(对应 Servlet 4.0 规范),Java 是 1.8 或 11
    - 如果 Dynamic Web Module 是灰色不可选,说明项目不是 Web 项目,需要先 Convert to faceted form...
    - Further configuration available...Content Directory 应该是 WebContentJava source folder 应该是 src

完成这些后,项目图标应该变成一个带小地球的 Java 图标,而不是普通的文件夹图标。右键项目 → Run AsRun on Server,选择你配置好的 Tomcat,点击 Finish。Eclipse 会自动启动 Tomcat,并在浏览器中打开 http://localhost:8080/your-project-name/index.jsp

4.2 代码编写与调试:从零开始的逐行验证

不要一上来就写完整功能。我推荐用“原子化验证”的方式,一步步确认每个环节:

第一步:验证 JSP 页面能正常访问
- 启动服务器后,浏览器打开 http://localhost:8080/your-project-name/index.jsp
- 确认页面显示一个按钮和一个空的 div。打开浏览器开发者工具(F12),切换到 Console 标签页,确认没有任何 JavaScript 错误。如果有 Uncaught ReferenceError: $ is not defined,说明 jQuery 路径错了;如果有 Uncaught SyntaxError,说明 JS 代码有语法错误。

第二步:验证 Servlet 能被正确映射
- 在浏览器地址栏直接访问 http://localhost:8080/your-project-name/TestServlet
- 如果看到一串 JSON 字符串 [{ "id": 1, "name": "张三", "age": 22 }, ...],说明 Servlet 配置成功。
- 如果看到 404,检查 web.xml<url-pattern> 和浏览器 URL 是否完全一致;检查 web.xml 是否放在 WebContent/WEB-INF/ 目录下;检查 TestServlet.java 是否在 src 目录下,且类名拼写正确。

第三步:验证 Ajax 请求能发出
- 在 index.jspsuccess 回调里,先不拼表格,只写 console.log(data);
- 点击按钮,查看 Console 输出。如果输出的是一个数组对象,说明请求成功,JSON 解析成功。
- 如果输出的是 undefinednull,检查 dataType: "json" 是否写了;如果输出的是字符串 "[{...}]",说明 dataType 没起作用,可能是 jQuery 版本太低或配置错误。

第四步:验证表格能动态生成
- 确认 console.log(data) 输出正确后,再把 tableHtml 拼接逻辑加上。
- 注意 $.each(data, function(index, person) { ... }) 中的 person 是一个 JS 对象,它的属性名必须和 Java Bean 的 getter 方法名一致(去掉 get 前缀,首字母小写),即 person.idperson.nameperson.age。如果 Java 类里是 getUserName(),那前端就要用 person.userName

4.3 关键参数计算与选择:为什么是这些值?

  • jQuery 版本选择 3.6.0:这是一个长期支持(LTS)版本,兼容 IE 9+,且 API 稳定。4.x 版本放弃了 IE 支持,对于教学环境来说,3.6.0 是兼容性与现代性的最佳平衡点。它足够新,支持 Promise 风格的 .done(),又足够老,不会引入太多新概念干扰主线。

  • Tomcat 端口 8080:这是 Tomcat 的默认 HTTP 端口。如果 8080 被占用(比如 Skype 或其他 Java 应用在用),可以在 conf/server.xml 里修改 <Connector port="8080" ... />8081,然后浏览器访问 http://localhost:8081/...。但教学时,建议先关掉冲突软件,保持默认端口,减少认知负担。

  • JSON 字符串长度限制json-lib 对单个 JSON 字符串没有硬性长度限制,但实际中,如果 List<Person> 有上万条数据,JSONArray.fromObject() 可能会因内存不足而 OutOfMemoryError。生产环境应分页,前端传 page=1&size=20,后端只查 20 条。这个项目只有 2 条,完全无需考虑。

  • 超时时间 5000ms:这是一个经验值。局域网内,一次 Servlet 请求通常在 100ms 内完成。设为 5 秒,既能覆盖网络抖动(比如 Wi-Fi 信号弱),又不会让用户等太久。如果项目部署在云服务器上,且数据库查询较慢,可以酌情提高到 10000

4.4 完整实操现场记录:一次成功的运行日志

以下是我刚刚在 Eclipse Oxygen + Tomcat 9.0.83 + JDK 11 环境下,从导入到成功运行的完整控制台日志(已过滤无关信息):

[INFO] Starting Servlet web server on port 8080
[INFO] Deploying web application archive [/path/to/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/your-project-name.war]
[INFO] At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
[INFO] Initializing Spring DispatcherServlet 'dispatcher'
[INFO] Completed initialization in 1 ms
[INFO] Server startup in [1234] milliseconds

浏览器访问 http://localhost:8080/your-project-name/index.jsp,点击按钮后,Chrome Console 输出:

// 点击按钮瞬间
XHR finished loading: GET "http://localhost:8080/your-project-name/TestServlet".

// success 回调中的 console.log(data)
Array(2)
  0: {id: 1, name: "张三", age: 22}
  1: {id: 2, name: "李四", age: 25}
  length: 2

// 表格渲染后,#table-container 的 innerHTML
<table border="1">
  <tr><th>序号</th><th>姓名</th><th>年龄</th></tr>
  <tr><td>1</td><td>张三</td><td>22</td></tr>
  <tr><td>2</td><td>李四</td><td>25</td></tr>
</table>

Tomcat 控制台(catalina.out)中,Servlet 的日志:

[INFO] [TestServlet] doGet called, returning 2 records

(这个日志需要你在 doGet() 方法开头加一行 System.out.println("[INFO] [TestServlet] doGet called, returning " + persons.size() + " records");

一切顺利。没有 404,没有 500,没有 parsererror,表格完美呈现。这就是一个“最小可行交互”的全部面貌。

5. 常见问题与排查技巧实录

5.1 经典问题速查表

现象可能原因排查步骤解决方案
点击按钮无反应,Console 无任何输出$(document).ready() 未执行,或 jQuery 未加载1. 查看 Network 标签页,确认 jquery-3.6.0.min.js 是否 404
2. 在 Console 输入 $,看是否返回函数
确保 js/jquery-3.6.0.min.js 路径正确,文件存在
Console 报错 Uncaught TypeError: Cannot read property 'click' of null$("#loadBtn") 找不到元素1. 查看 Elements 标签页,确认 <button id="loadBtn"> 是否存在
2. 确认 $(document).ready() 是否包裹了事件绑定
检查 HTML 中 id 拼写,确保 $(document).ready() 存在
浏览器访问 /TestServlet 返回 404web.xml 配置错误,或 Servlet 类未编译1. 确认 web.xmlWebContent/WEB-INF/
2. 确认 TestServlet.javasrc 下,且类名匹配
3. 查看 Eclipse Problems 视图是否有编译错误
修正 web.xml<servlet-class><url-pattern>,确保无拼写错误
访问 /TestServlet 返回 500,Console 显示 java.lang.NoClassDefFoundError: net/sf/json/JSONArrayjson-lib 及其依赖 jar 包缺失1. 查看 WebContent/WEB-INF/lib/ 目录
2. 确认 json-lib-2.4-jdk15.jar 等 5 个 jar 是否都在
将缺失的 jar 包复制到 lib 目录,右键 → Build PathAdd to Build Path
Console 显示 parsererrorerror 回调中 status"parsererror"Servlet 返回的不是合法 JSON1. 直接访问 /TestServlet,看返回内容是否是纯 JSON 字符串
2. 用在线 JSON 校验工具(如 jsonlint.com)粘贴返回内容
检查 response.setContentType("application/json;charset=UTF-8") 是否写了;检查 JSONArray.fromObject() 是否传入了正确的 List
表格显示,但中文是 ?? 或乱码字符编码不一致1. 查看响应头 Content-Type 是否为 application/json;charset=UTF-8
2. 查看 index.jsp<%@ page contentType="text/html; charset=UTF-8" %>
确保 Servlet 和 JSP 的 charset=UTF-8 都写了,且 web.xml<jsp-config> 也配置了 page-encoding

5.2 我踩过的坑与独家避坑技巧

坑一:web.xml 放错了位置
有一次,我把 web.xml 放在了 src 目录下,Eclipse 项目结构看着没问题,但 Tomcat 启动后死活找不到 Servlet。排查了两个小时,最后发现 web.xml 必须在 WebContent/WEB-INF/ 下,这是 Servlet 规范的硬性要求。避坑技巧:在 Eclipse 的 Project Explorer 视图中,展开项目,你应该能看到 WebContent > WEB-INF > web.xml 这个路径。如果 WEB-INF 文件夹是红色的(表示不存在),说明项目 Facets 没配好。

坑二:Person 类的 getter 方法名不规范
我写了一个 getUserName() 方法,但前端一直取不到 userName,而是 undefined。后来发现 json-lib 的反射机制要求 getter 方法名必须是 getXxx(),且 Xxx 的首字母必须大写,对应的 JSON 属性名是 xxx(首字母小写)。getUserName() 会映射到 userName,但 getusername() 就会映射失败。避坑技巧:在 Person 类上右键 → SourceGenerate Getters and Setters...,让 Eclipse 自动生成,确保命名绝对规范。

坑三:Tomcat 启动后,修改了 Java 代码,但浏览器没变化
这是新手最懵的时刻。原因很简单:Tomcat 启动时,会把 src 下的 .java 文件编译成 .class,放到 build/classesWebContent/WEB-INF/classes 下。如果你只是改了 .java,但没触发重新编译,Tomcat 还在运行旧的 .class避坑技巧:在 Eclipse 中,右键项目 → Refresh,然后右键项目 → Run AsRedeploy。或者,更简单,关闭 Tomcat,再右键项目 → Run AsRun on Server,Eclipse 会自动检测变更并重新部署。

坑四:$.ajax()url 写成了绝对路径 /TestServlet
我一开始写 url: "/TestServlet",结果请求发到了 http://localhost:8080/TestServlet,而不是 http://localhost:8080/your-project-name/TestServlet,导致 404。因为 / 表示根路径,而你的应用上下文是 /your-project-name避坑技巧:永远用相对路径 url: "TestServlet"。它会相对于当前页面的 URL。index.jsp 的 URL 是 /your-project-name/index.jsp,所以 TestServlet 就是 /your-project-name/TestServlet

坑五:JSONArray.fromObject() 返回空数组 []
数据明明有两条,但 JSON 字符串却是 []。最后发现,Person 类的属性是 private 的,但 json-lib 需要 public 的 getter。我只写了 private String name;,忘了写 public String getName() { return name; }避坑技巧:在 Person 类上,按 Alt+Shift+SR → 选择所有属性 → Generate Getters and Setters,一劳永逸。

5.3 进阶调试:用浏览器 Network 面板做侦探

当一切看起来都对,但就是不工作时,Network 面板是你的终极武器。以 Chrome 为例:

  1. 打开开发者工具(F12),切换到 Network 标签页。
  2. 点击 Filter 输入框,输入 TestServlet,只显示相关请求。
  3. 点击按钮,你会看到一条 TestServlet 的请求记录。
  4. 点击它,右侧会显示详细信息:
    - Headers:看 Request URL 是否正确;看 Response Headers 里的 Content-Type 是否为 application/json;charset=UTF-8;看 Status Code 是否为 200
    - Preview:这是浏览器解析后的 JSON 视图,如果这里显示 Failed to load response data,说明响应体不是合法 JSON。
    - Response:这是原始响应体字符串,复制出来,粘贴到 jsonlint.com,一键校验合法性。
    - Timing:看 Waiting (TTFB) 时间,如果超过 5 秒,说明后端处理慢;看 Content Download,如果为 0,说明响应体为空。

我曾经遇到一个诡异问题:Preview 显示正常 JSON,但 Response 里却有一堆乱码。最后发现是 response.setContentType() 写成了 "application/json;charset=GBK",而前端期望 UTF-8。Network 面板的 Response Headers 一栏,把这个问题暴露得清清楚楚。

6. 项目结构深度解析与目录树含义

6.1 标准 Web 应用目录结构的“宪法”

这个项目的目录树,不是随意组织的,而是遵循了 Java EE 规范的“宪法级”约定。理解每个目录的法定职责,是避免“文件放错地方导致 404”的前提:

oAx1xDrd3k8b7hlrkG0A-master-75458cc4805dde9702c5b4dabbdcc03eecf3a5ee/
├── src/                          # 【源码区】Java 源代码 (.java 文件) 存放地。Tomcat 不会直接读这里,Eclipse 编译后会把 .class 放到 classes 目录。
│   └── TestServlet.java          # Servlet 类,必须在此目录或其子包下。
├── WebContent/                   # 【发布区】所有会被 Tomcat 直接发布的资源。浏览器能通过 URL 访问的,都必须放在这里。
│   ├── index.jsp                 # 主页面,是整个应用的入口。
│   ├── js/                       # 【静态资源】JavaScript 文件。jQuery 必须放这里,路径才对。
│   │   └── jquery-3.6.0.min.js
│   ├── WEB-INF/                  # 【禁区】Tomcat 的“保险柜”,浏览器无法直接访问。里面放 web.xml、classes、lib。
│   │   ├── web.xml               # 【宪法】Servlet 的注册中心,定义了所有 URL 映射。
│   │   ├── classes/              # 【编译产物】Eclipse 编译 src/ 下的 .java 后,.class 文件放这里。Tomcat 运行时只读这个目录。
│   │   └── lib/                  # 【依赖库】所有第三方 jar 包(json-lib 等)必须放这里。Tomcat 启动时自动加载。
│   └── META-INF/                 # 【元数据】存放 MANIFEST.MF 等描述文件,普通项目可忽略。
├── .gitignore                    # 【版本控制】告诉 Git 哪些文件不用上传(如 .class, .settings)。
└── .inscode                      # 【IDE 配置】IntelliJ IDEA 的配置文件,Eclipse 可忽略。

关键点在于 WebContentsrc 的分工。src 是“原料车间”,WebContent 是“成品仓库”。Eclipse 的魔法在于,它会监听 src 的变化,自动编译,然后把 .class 文件“搬运”到 WebContent/WEB-INF/classes/ 下。所以,你永远不要手动去 classes 目录里放东西,也不要试图把 .java 文件放到 WebContent 下——那是违法的,Tomcat 不会认。

6.2 classes 目录:编译产物的“法定居所”

WebContent/WEB-INF/classes/ 是一个神圣不可侵犯的目录。它的存在,是 Java Web 能运行的基石。当你在 Eclipse 中写完 TestServlet.java,按下 Ctrl+S 保存,Eclipse 会立刻做三件事:

  1. 调用 javac 编译 src/TestServlet.java,生成 TestServlet.class
  2. TestServlet.class 复制到 WebContent/WEB-INF/classes/ 目录下。
  3. 如果 TestServlet 在包里(比如 com.example.servlet),Eclipse 会自动创建 WebContent/WEB-INF/classes/com/example/servlet/ 目录,并把 .class 放进去。

Tomcat 启动时,会把这个 classes 目录加入到 ClassPath 中。所以,当 web.xml<servlet-class>TestServlet</servlet-class>,Tomcat 就会在 classes 目录下找 TestServlet.class。如果 classes 目录下没有这个文件,或者路径不对(比如你在 src 里写了 package com.example;,但 classes 目录下是 TestServlet.class 而不是 com/example/TestServlet.class),Tomcat 就会报 ClassNotFoundException

一个血泪教训: 有一次,我把 TestServlet.java 放在了 src 的根目录,但 Eclipse 的 Build Path 设置里,Default output folder 被错误地指向了 bin/ 目录,而不是 WebContent/WEB-INF/classes/。结果 Tomcat 启动后,classes 目录是空的,死活找不到 Servlet。解决方案:右键项目 → PropertiesJava Build PathSourceDefault output folder,必须是 your-project-name/WebContent/WEB-INF/classes

6.3 lib 目录:第三方依赖的“户籍所在地”

WebContent/WEB-INF/lib/ 是所有 jar 包的法定户籍所在地。为什么必须放这里?因为 Servlet 规范规定,容器(Tomcat)在启动一个 Web 应用时,会自动扫描 WEB-INF/lib/ 下的所有 .jar 文件,并将它们的 Class-Path 添加到该应用的 ClassLoader 中。这意味着,json-libJSONArray 类,对 TestServlet 来说是“可见的”。

如果你把 json-lib-2.4-jdk15.jar 放在了 src 目录下,Eclipse 编译时可能会找到它(因为 Build Path 里加了),但 Tomcat 运行时,ClassLoader 根本看不到它,就会在 JSONArray.fromObject() 这一行抛 NoClassDefFoundError

如何确认 jar 包已生效? 启动 Tomcat 后,打开 http://localhost:8080/manager/html(需要配置 manager 用户),在应用列表里找到你的项目,点击 Find leaks。如果 lib 下的 jar 包被正确加载,就不会出现在泄漏列表里。更简单的方法:在 TestServlet.doGet() 里写一行 System.out.println(JSONArray.class);,如果控制台打印出 class net.sf.json.JSONArray,说明 jar 包加载成功。

7. 项目扩展与后续演进路径

7.1 从“两行数据”到“真实业务”的平滑升级

这个项目演示的是最简场景,但它是一块完美的跳板。我可以分享几个我带学生做过的、零成本的升级案例:

升级一:支持 POST 请求与表单提交
index.jsp 的按钮换成表单:

<form id="searchForm">
    <input type="text" name="name" placeholder="输入姓名搜索">
    <button type="submit">搜索</button>
</form>
<div id="table-container"></div>

在 JS 里:

$("#searchForm").submit(function(e) {
    e.preventDefault(); // 阻止默认提交
    var formData = $(this).serialize(); // 自动序列化为 name=张三
    $.ajax({
        url: "TestServlet",
        type: "POST",
        data: formData,
        dataType: "json",
        success: function(data) { ... }
    });
});

TestServlet.java 里,重写 doPost()

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    String name = request.getParameter("name"); // 获取表单参数
    List<Person> persons = searchByName(name); // 调用 DAO 查询数据库
    response.setContentType("application/json;charset=UTF-8");
    response.getWriter().write(JSONArray.fromObject(persons).toString());
}

升级二:添加加载动画与错误重试
提升用户体验:

$("#loadBtn").click(function() {
    $("#loadBtn").prop("disabled", true).text("加载中...");
    $("#table-container").html("<p>正在努力加载...</p>");

    $.ajax({
        url: "TestServlet",
        type: "GET",
        dataType: "json",
        timeout: 5000,
        success: function(data) {
            // 渲染表格
        },
        error: function(xhr, status, error) {
            if (status === "timeout") {
                $("#table-container").html("<p>网络超时,请重试</p>");
                $("#loadBtn").prop("disabled", false).text("重试");
            } else {
                $("#table-container").html("<p>加载失败:" + error + "</p>");
                $("#loadBtn").prop("disabled", false).text("重试");
            }
        }
    });
});

升级三:集成数据库(MySQL)
这才是真实世界的起点。只需要三步:
1. 在 WEB-INF/lib/ 加入 mysql-connector-java-8.0.33.jar
2. 在 TestServlet.doGet() 里,用 DriverManager.getConnection() 连接数据库,执行 SELECT * FROM person
3. 把 ResultSet 里的数据,一行行 new Person(...),加入 List<Person>

整个过程,你不需要改任何前端代码,$.ajax 和表格渲染逻辑完全复用。这就是前后端分离的魅力:后端换数据库,前端无感。

7.2 与现代框架的衔接点

别以为 JSP + Servlet 是“古董”,它和 Spring MVC、Spring Boot 是血脉相连的。TestServlet 就是 @Controller 的雏形,web.xml 就是 @SpringBootApplication 的配置源头。当你学会了手动写 response.getWriter().write(jsonStr),再去看 Spring 的 @ResponseBody,就会豁然开朗——它不过是帮你自动做了 setContentTypewrite() 而已。

我建议的学习路径是:先用这个项目打通“请求-处理-响应”的任督二脉,再用 Spring Boot 的 @RestController 重写一遍。你会发现,Spring Boot 的魔法,只是把 web.xmlservlet-classresponse.getWriter() 这些样板代码,用注解和自动配置封装起来了。 知道了底层怎么跑,用框架时才不会被黑盒吓住。

7.3 个人经验总结:为什么我坚持教这个“老技术”

在我带的第 7 届学生里,有一个叫小王的同学,他花了两周时间,愣是没搞懂 Vue 的 axios 怎么和后端交互。直到我让他放下 Vue,用这个 JSP 项目,从 web.xml 开始,一行行断点调试,他才第一次真正理解了“HTTP 请求是怎么从浏览器飞到服务器的”、“Servlet 容器是怎么根据 URL 找到 Java 类的”、“JSON 字符串是怎么变成 JS 对象的”。两周后,他不仅搞定了 Vue,还主动帮同学排查 CORS 问题。

这个项目的价值,不在于它能做什么酷炫的功能,而在于它把 Web 开发中最核心的“通信契约”——HTTP 协议、URL 路由、MIME 类型、字符编码、JSON 格式——全都摊开在阳光下,没有任何封装,没有任何魔法。它像一台透明的发动机,让你看清每一个活塞的运动。当你亲手把 response.setContentType("application/json;charset=UTF-8") 这行代码敲出来,并亲眼看到它如何让中文不再乱码时,那种“啊哈!”的顿悟,是任何框架文档都无法给予的。

所以,如果你现在正被某个现代框架的配置搞得焦头烂额,不妨暂停一下,回到这个简单的 JSP + Servlet 项目。把它从头到尾,一行代码、一个配置、一个目录,都亲手过一遍。你会发现,那些曾经高深莫测的概念,突然都变得亲切而具体。这,就是技术学习最扎实的根基。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:JSP页面通过jQuery的$.ajax方法向后端Servlet发起异步请求,Servlet接收请求后将Java对象转换为标准JSON格式(含序号、姓名、年龄三字段),返回两行模拟数据(如:1,张三,22;2,李四,25)。前端接收到JSON响应后,不刷新页面,直接解析数据并拼接HTML字符串,动态插入到页面指定容器中渲染成表格。整个流程涵盖前端页面(index.jsp)、后端处理逻辑(TestServlet.java)、Web配置(web.xml隐含在facets中)、JavaScript依赖(js目录下jQuery等)、编译输出路径(classes)及标准Web目录结构(WEB-INF、META-INF等),支持Eclipse环境一键导入运行,适合学习前后端分离式异步交互的入门实践。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

本文章已经生成可运行项目

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

内容概要:本文系统阐述了嵌入式功能安全领域的两大核心标准——IEC 61508与ISO 26262的完整体系,涵盖其定位、关系、技术要求及认证流程。IEC 61508作为通用工业功能安全基础标准,适用于PLC、机器人、轨道交通等系统,采用SIL等级划分;ISO 26262则是其在汽车行业的衍生标准,专用于车载电控单元(如BMS、ESP、自动驾驶控制器),采用ASIL等级评估。文章详细解析了两个标准在风险评估方法(如HARA与风险图法)、软硬件设计规范、失效分析、安全机制实现(如看门狗、CRC校验、冗余设计)等方面的异同,提供了从需求分析到认证落地的全流程实施路径,包括安全生命周期管理、文档证据链构建及第三方认证机构介绍。; 适合人群:从事工业自动化或汽车电子领域嵌入式系统设计、功能安全开与认证工作的工程师、项目经理及安全分析师,具备一定电子电气或软件开背景的专业人员; 使用场景及目标:①指导企业开展符合IEC 61508或ISO 26262的功能安全产品设计与认证;②帮助研团队理解SIL/ASIL等级判定逻辑与软硬件安全机制实现方式;③支持撰写安全需求文档、FMEDA报告及准备第三方审核材料; 阅读建议:此资源兼具理论体系与工程实践,建议结合具体项目场景对照标准条款进行研读,重点关注安全生命周期各阶段的交付物要求与典型安全防护设计示例,以提升实际应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值