传统JSP项目中JavaScript怎么获取后端数据?

作者:终端旅客

时间:2026-10-30

浏览:329

深入解析传统JSP与Servlet项目中前端JavaScript获取后端数据的四种主流途径:EL表达式内联赋值、DOM与隐藏域中转、Ajax异步通信以及全局配置对象挂载,帮助开发者厘清服务端与客户端生命周期,规避中文乱码与XSS安全风险。

文章目录
在传统的 Java Web(JSP + Servlet)开发架构中,前端 JavaScript 获取后端数据主要分为两大工作路径:服务端首屏渲染时的同步静态注入与页面加载后的客户端异步通信(Ajax)。理解这两种路径的核心在于厘清“服务端运行期”与“客户端运行期”的执行时序。本文将系统梳理四种标准取值方案,并给出实际开发中的防坑指南。

一、核心认知:JSP服务端解析与浏览器JS执行时机

很多刚接触 JSP 的开发者经常会写出类似 <script>var val = request.getParameter("name");</script> 的错误代码,误以为 JavaScript 能够直接调用后端的 Java 变量。这种混淆的根源在于未能区分两者的生命周期与运行环境。

JSP 本质上是一个运行在 Servlet 容器(如 Tomcat)中的模板文件。当浏览器发出页面请求时,Tomcat 内部会将 JSP 文件转译为 Java Servlet 类并编译执行。在这个阶段,所有以 <% ... %>、<%= ... %> 和 ${...} 形式存在的 EL 表达式与 Java 代码都会被服务端计算并替换为普通的文本内容。替换完成后的整体 HTML 文本通过网络流传输给浏览器,浏览器的 JavaScript 引擎才开始解析并执行 <script> 标签内的逻辑。

传统JSP项目中JavaScript怎么获取后端数据?
图1:JSP服务端解析与客户端执行生命周期分层架构

二、方案一:JSP表达式 / EL表达式直接内联注入

直接内联注入是传统 JSP 项目中最直接、最简易的取值方式。利用 EL 表达式 ${} 或 JSP 输出表达式 <%= %>,在页面的 <script> 块中将后端存入 request 或 session 中的属性值直接渲染为 JavaScript 字面量。

1. 基础单值传递(数值与字符串)

对于整型、浮点型或布尔值,可以直接作为数值字面量输出;对于字符串类型,则必须在表达式外部包裹成对的英文单引号或双引号:

<%-- 后端 Servlet 注入数据 --%>
<%
    request.setAttribute("isVip", true);
    request.setAttribute("userId", 10023);
    request.setAttribute("userName", "张三");
%>

<script>
    // 字符串必须用引号包裹,否则会导致语法错误: Uncaught ReferenceError: 张三 is not defined
    var currentUserName = "${userName}";
    var currentUserId = ${userId}; 
    var isUserVip = ${isVip};

    console.log("用户信息:", currentUserName, currentUserId, isUserVip);
</script>

2. 复杂对象与集合传递(转为 JSON 字符串注入)

如果后端数据是 Java List 或自定义 JavaBean 对象,直接使用 ${user} 只会打印对象的 toString() 结果(如 com.example.User@3f12a),无法在 JavaScript 中作为对象使用。此时需要在后端将对象序列化为标准 JSON 字符串,再写入请求域:

// Java 后端 (Servlet / Controller)
List<User> userList = userService.findAllUsers();
String jsonStr = new com.google.gson.Gson().toJson(userList); // 或使用 Fastjson / Jackson
request.setAttribute("userListJson", jsonStr);

在 JSP 页面中,通过 JSON.parse() 解析注入的字符串:

<script>
    // 方式 A:注入为原始 JSON 字符串再解析(推荐,语法检查更友好)
    var rawJson = '${userListJson}';
    var userList = JSON.parse(rawJson);

    // 方式 B:直接作为对象字面量内联(注意避免 IDE 语法报错)
    var userListDirect = ${userListJson};
    
    console.log("第一个用户姓名:", userList[0].name);
</script>
注意安全陷阱(XSS与引号闭合):
若字符串变量包含双引号、单引号或反斜杠(如用户的个性签名 Hello "World"),直接在 var str = "${userSign}"; 中拼接会导致 JavaScript 语法被提前闭合截断,引发报错甚至形成反射型 XSS 漏洞。生产环境中务必对字符串进行转义,或在 JSTL 中使用 fn:escapeXml()。

三、方案二:利用隐藏表单域与HTML5 Data属性中转

内联注入虽然直观,但其最大缺陷在于导致 JavaScript 强依赖 JSP 运行环境。一旦逻辑复杂,脚本无法提取为独立的外部 .js 文件进行浏览器缓存和工程化维护。为了实现结构与行为的分离,推荐采用 DOM 属性中转方式。

传统JSP项目中JavaScript怎么获取后端数据?
图2:利用隐藏域与HTML5 Data属性中转数据架构

1. 隐藏表单域(Hidden Input)中转

隐藏域常用于表单提交、防重复提交令牌(CSRF Token)或单实体 ID 传递:

<%-- JSP 视图文件 --%>
<input type="hidden" id="page-user-id" value="${user.id}" />
<input type="hidden" id="app-context-path" value="${pageContext.request.contextPath}" />

<!-- 引入独立的外部 JS 文件 -->
<script src="${pageContext.request.contextPath}/static/js/app.js"></script>
// 独立的 static/js/app.js 文件
document.addEventListener("DOMContentLoaded", function () {
    // 原生 JavaScript 读取
    var userId = document.getElementById("page-user-id").value;
    var contextPath = document.getElementById("app-context-path").value;

    // 或者使用 jQuery 读取
    // var userId = $("#page-user-id").val();

    console.log("从隐藏域获取的基础信息:", userId, contextPath);
});

2. HTML5 Data-* 自定义数据集属性

如果需要在特定的 DOM 元素(如商品列表卡片、用户信息面板)上绑定多项属性,使用 data-* 属性语义更明确且易于维护:

<%-- JSP 中列表循环渲染 --%>
<c:forEach items="${productList}" var="product">
    <div class="product-item" 
         data-id="${product.id}" 
         data-sku="${product.sku}" 
         data-price="${product.price}" 
         data-title="${fn:escapeXml(product.title)}">
        <h3>${product.title}</h3>
        <button class="btn-buy">购买</button>
    </div>
</c:forEach>
// static/js/product.js
document.querySelectorAll(".product-item").forEach(function (card) {
    card.querySelector(".btn-buy").addEventListener("click", function () {
        // 通过 DOM 的 dataset 属性读取自定义值
        var productId = card.dataset.id;
        var productSku = card.dataset.sku;
        var productPrice = card.dataset.price;

        console.log("准备结算商品:", productId, "单价:", productPrice);
    });
});

四、方案三:通过Ajax异步向Servlet/Controller请求数据

当页面首屏加载完毕后,由于用户的点击、滚动或表单联动操作需要刷新部分数据,前两种首屏同步方式就无能为力了。此时必须通过 Ajax(Asynchronous JavaScript and XML)发起异步 HTTP 请求获取数据。

传统JSP项目中JavaScript怎么获取后端数据?
图3:前端Ajax向后端Servlet异步拉取数据交互流程

1. 后端 Servlet 输出纯 JSON 响应

传统的 Servlet 往往通过 RequestDispatcher.forward() 转发页面,但在处理异步接口时,必须改为向 HttpServletResponse 输出纯文本,并设置标准 MIME 类型与字符集:

@WebServlet("/api/user/info")
public class UserApiServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
            throws ServletException, IOException {
        // 关键设置:指定返回数据格式为 JSON 并统一使用 UTF-8 编码,防止中文乱码
        response.setContentType("application/json;charset=UTF-8");
        
        String userId = request.getParameter("id");
        
        // 模拟业务对象
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "查询成功");
        result.put("data", Map.of("id", userId, "name", "李四", "level", "VIP2"));
        
        // 将 Map 转换为 JSON 文本输出
        PrintWriter out = response.getWriter();
        out.write(new Gson().toJson(result));
        out.flush();
    }
}

2. 前端发起 Ajax 异步调用

在传统的 JSP 技术栈中,通常引入了 jQuery。使用 $.ajax 或 $.getJSON 可以非常方便地获取后端数据:

// 使用 jQuery 封装的 $.ajax
function loadUserInfo(userId) {
    $.ajax({
        url: "${pageContext.request.contextPath}/api/user/info",
        type: "GET",
        data: { id: userId },
        dataType: "json", // 自动解析为 JavaScript 对象
        success: function (res) {
            if (res.code === 200) {
                // 动态更新页面 DOM 元素,无需刷新整个 JSP
                $("#user-title").text(res.data.name + " (" + res.data.level + ")");
            } else {
                alert("加载失败: " + res.msg);
            }
        },
        error: function (xhr, status, error) {
            console.error("网络请求异常:", error);
        }
    });
}
动态上下文路径提示:
在书写 Ajax 的 URL 时,切勿将项目访问前缀写死(如 url: "/myproject/api/user"),因为项目部署到生产环境后 Context Path 可能会改变。应始终借助 ${pageContext.request.contextPath} 拼接动态前缀。

五、方案四:服务端全局配置对象挂载

在一个中大型传统 Web 体系中,通常有许多公共数据(例如项目根路径、静态资源 CDN 地址、当前登录人基准信息、数据字典、页面权限开关)。如果每个组件都各自去定义隐藏域或发请求,代码会极度臃肿。业界常见做法是在通用的母版(如 header.jsp)中挂载一个全局配置对象。

<%-- 通用 header.jsp 顶部代码 --%>
<script>
    // 挂载至全局 window 对象的专属命名空间,防止命名冲突
    window.__APP_CONFIG__ = {
        contextPath: "${pageContext.request.contextPath}",
        currentUser: {
            id: "${sessionScope.currentUser.id}",
            role: "${sessionScope.currentUser.role}",
            isAdmin: ${sessionScope.currentUser.isAdmin ? true : false}
        },
        serverTime: ${System.currentTimeMillis()},
        env: "production"
    };
</script>

定义完毕后,后续引入的所有独立外部 .js 文件均可随时读取全局配置,而无需与具体的 JSP 标签发生耦合:

// 任意业务 js 文件 (如 order.js)
function submitOrder() {
    var userId = window.__APP_CONFIG__.currentUser.id;
    var postUrl = window.__APP_CONFIG__.contextPath + "/order/create";
    
    // 发起业务逻辑
    console.log("准备向接口提交订单:", postUrl, "用户ID:", userId);
}

六、四种方案的对比与选型指南

为了帮助读者根据实际项目场景准确选择方案,下表及图示从加载时机、对页面刷新的依赖、耦合度与典型适用场景四个维度进行横向对比:

传统JSP项目中JavaScript怎么获取后端数据?
图4:四种后端数据获取方案横向选型矩阵

总结与工程落地建议

  1. 首选结构解耦:如果是页面初次渲染所需的数据,优先使用 <input type="hidden"> 或 data-* 属性传递,保持业务 JS 为纯粹的静态文件。
  2. 统一基础上下文:对于项目路径 contextPath、API 域名与登录用户基础信息,统一在公用的 header.jsp 中以挂载 window.__APP_CONFIG__ 的形式输出,避免各处硬编码。
  3. 动态交互交由 Ajax:涉及用户触发的查询、下拉联动、异步提交,严禁刷新整页,应编写独立的 Servlet 输出 application/json 数据,由前端异步回调完成渲染。
  4. 安全防御常态化:任何输出到 HTML 属性或 JavaScript 脚本变量中的字符串,都应始终警惕 XSS 漏洞,规范使用字符转义工具,杜绝拼接未经校验的外部输入内容。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章