传统JSP项目中JavaScript怎么获取后端数据?
深入解析传统JSP与Servlet项目中前端JavaScript获取后端数据的四种主流途径:EL表达式内联赋值、DOM与隐藏域中转、Ajax异步通信以及全局配置对象挂载,帮助开发者厘清服务端与客户端生命周期,规避中文乱码与XSS安全风险。
一、核心认知: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表达式 / 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>
若字符串变量包含双引号、单引号或反斜杠(如用户的个性签名
Hello "World"),直接在 var str = "${userSign}"; 中拼接会导致 JavaScript 语法被提前闭合截断,引发报错甚至形成反射型 XSS 漏洞。生产环境中务必对字符串进行转义,或在 JSTL 中使用 fn:escapeXml()。
三、方案二:利用隐藏表单域与HTML5 Data属性中转
内联注入虽然直观,但其最大缺陷在于导致 JavaScript 强依赖 JSP 运行环境。一旦逻辑复杂,脚本无法提取为独立的外部 .js 文件进行浏览器缓存和工程化维护。为了实现结构与行为的分离,推荐采用 DOM 属性中转方式。
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 请求获取数据。
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);
}
六、四种方案的对比与选型指南
为了帮助读者根据实际项目场景准确选择方案,下表及图示从加载时机、对页面刷新的依赖、耦合度与典型适用场景四个维度进行横向对比:
总结与工程落地建议
- 首选结构解耦:如果是页面初次渲染所需的数据,优先使用
<input type="hidden">或data-*属性传递,保持业务 JS 为纯粹的静态文件。 - 统一基础上下文:对于项目路径
contextPath、API 域名与登录用户基础信息,统一在公用的header.jsp中以挂载window.__APP_CONFIG__的形式输出,避免各处硬编码。 - 动态交互交由 Ajax:涉及用户触发的查询、下拉联动、异步提交,严禁刷新整页,应编写独立的 Servlet 输出
application/json数据,由前端异步回调完成渲染。 - 安全防御常态化:任何输出到 HTML 属性或 JavaScript 脚本变量中的字符串,都应始终警惕 XSS 漏洞,规范使用字符转义工具,杜绝拼接未经校验的外部输入内容。
Java和JavaScript的应用场景分工:后端企业级开发与Web前端交互
2026-11-05 15:57:34
除了浏览器和Node.js,还有哪些轻量级Js脚本运行工具?
2026-11-04 17:38:24
无需后端:用JavaScript实现简单网站的本地数据存储
2026-11-03 23:03:30
Node.js 下载慢或官网打不开怎么办
2026-11-03 18:19:37
JS解析大体积Excel文件内存溢出怎么办
2026-11-03 18:19:37








