在本系列的
第 1 部分 中,您学习了如何在 Asynchronous JavaScript and XML(Ajax)和
Java™ Platform, Enterprise Edition(Java EE)应用程序中使用
javax.script API。本文将展示如何为同时在服务器和客户机上使用 JavaScript 的
Web 应用程序实现远程过程调用(Remote Procedure Call,RPC)机制。您还将学习一些有趣的技巧,例如用
JavaScript 实现 Java 接口、构建 XMLHttpRequest 包装器、使 Ajax 调试更加容易以及使用
JSP 标记文件生成 JavaScript 代码。
RPC 机制非常简单。服务器上有一组 JavaScript 函数,现在希望能从 Web 浏览器调用它们,就像有一个
JavaScript 引擎在同时执行客户机代码和服务器代码一样。因此,需要有一些客户端例程,这些例程应该与服务器端的对应例程有相同的名称和参数。
客户端例程将使用 Ajax 把它们的参数传递到服务器,在服务器上进行真正的处理。一个 Java servlet
将调用服务器端函数,并使用 JSON 格式将结果返回到客户机。然后,客户端例程计算 Ajax 响应,将
JSON 字符串转换回 JavaScript 对象,后者被返回到应用程序。
作为应用程序开发人员,可以将精力放在构建用户界面和服务器中执行的函数上。您不需要处理 Ajax 或 RPC
问题,因为本文以标记文件的形式提供了一个 JavaScript 代码生成器,您可以在 JSP 页面使用它来自动生成客户端例程。为了理解其中的工作原理,我们从一个示例应用程序开始。
本文中的示例应用程序使用 Java Management API 监视运行托管应用程序的 Java EE
服务器的 JVM。用户界面由一个简单的 Web 页面组成,该页面显示各种不同的指示器,例如 Java 类的数量、内存消耗、垃圾收集器的活动和线程的数量。
这些信息通过 Ajax 获得,并插入到一个 HTML 表中(如图 1 所示;单击
这里 查看图 1 的放大图)。更有趣的是,这个 Web 页面包含一个表单,通过该表单可以为给定的秒数分配内存。还可以调用
JVM 的垃圾收集器(GC)。
图 1. 示例应用程序
在服务器端,应用程序使用一个 JavaScript 文件,Web 浏览器将借助 Ajax 并使用本系列第
1 部分提供的脚本运行器调用该文件中的函数。这是一个简单的 servlet,它处理 URL 以 .jss
扩展名结束的所有请求。该 servlet 查找服务器上相应的 JavaScript 文件,并使用 Java
Scripting API 执行它。
创建 Web 页面
清单 1 显示 MonitorPage.jsp 文件中的表和表单。每个数据单元有一个 ID,以便用 JavaScript
更新表中的内容。<body> 标记的
onload 属性用于调用一个名为 init()
的 JavaScript 函数,该函数将初始化应用程序的客户端部分。另外两个函数 allocMem()
和 gc() 是在用户单击按钮时调用的。
清单 1. MonitorPage.jsp 的 HTML 代码
<html>
<head>
...
<style type="text/css">
th { text-align: left; }
td { text-align: right; }
.space { margin: 10px; }
</style>
</head>
<body onload="init()">
<table border="1" cellpadding="5">
<tr>
<th colspan=2>Classes</th>
<th colspan=2>Heap Memory</th>
<th colspan=2>Non-Heap Memory</th>
<th colspan=2>Garbage Collector</th>
<th colspan=2>Threads</th>
</tr>
<tr>
<th>Loaded</th>
<td id="info.classes.loaded"></td>
<th>Used</th>
<td id="info.memory.heap.used"></td>
<th>Used</th>
<td id="info.memory.nonHeap.used"></td>
<th>Count</th>
<td id="info.gc.count"></td>
<th>Live</th>
<td id="info.threads.live"></td>
</tr>
<tr>
<th>Unloaded</th>
<td id="info.classes.unloaded"></td>
<th>Committed</th>
<td id="info.memory.heap.committed"></td>
<th>Committed</th>
<td id="info.memory.nonHeap.committed"></td>
<th>Time</th>
<td id="info.gc.time"></td>
<th>Peak</th>
<td id="info.threads.peak"></td>
</tr>
</table>
<br>
<form name="monitorForm">
Size: <input name="size" size="10">
<span class="space"></span>
Seconds: <input name="seconds" size="4">
<span class="space"></span>
<button type="button"
onclick="allocMem(this.form.size.value, this.form.seconds.value)"
>Allocate Memory</button>
<span class="space"></span>
<button type="button" onclick="gc()">Collect Garbage</button>
</form>
</body>
</html>
|
MonitorPage.jsp 文件(见清单 2)使用一个名为 <js:rpc>
的定制标记来生成客户端 JavaScript 函数,这些函数调用服务器端对应的函数。<js:rpc>
标记有以下属性:
script |
将在服务器上执行的脚本的 URL |
function |
被远程调用的 JavaScript 函数的签名 |
validator |
一个可选表达式,在 Web 浏览器中将计算该表达式以验证函数的参数 |
jsonVar |
一个可选 JavaScript 变量的名称,该变量用于存储 JSON 响应 |
method |
HTTP 方法,可以是 GET 或 POST |
async |
表明应该异步还是同步使用 XMLHttpRequest
的一个布尔属性 |
清单 2. MonitorPage.jsp 的 JavaScript
代码
<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>
<html>
<head>
<title>Monitor</title>
<script src="xhr.js" type="text/javascript">
</script>
<script type="text/javascript">
<js:rpc function="getInfo()" script="MonitorScript.jss"
method="GET" async="true" jsonVar="json">
showInfo(json, "info");
</js:rpc>
<js:rpc function="allocMem(size, seconds)" script="MonitorScript.jss"
validator="valid('Size', size) && valid('Seconds', seconds)"
method="POST" async="true"/>
<js:rpc function="gc()" script="MonitorScript.jss"
method="POST" async="false">
alert("Garbage Collected");
</js:rpc>
function showInfo(obj, id) {
if (typeof obj == "object") {
for (var prop in obj)
showInfo(obj[prop], id + "." + prop);
} else {
var elem = document.getElementById(id);
if (elem)
elem.innerHTML = htmlEncode(String(obj));
}
}
function valid(name, value) {
if (!value || value == "") {
alert(name + " is required");
return false;
}
var n = new Number(value);
if (isNaN(n) || n <= 0 || Math.floor(n) != n) {
alert(name + " must be a positive integer.");
return false;
} else
return true;
}
function init() {
getInfo();
setInterval(getInfo, 1000);
}
</script>
...
</head>
...
</html>
|
<js:rpc> 与 </js:rpc>
之间的 JavaScript 代码将用于处理 Ajax 响应。例如,对于 getInfo()
函数,json 响应被传递到另一个名为 showInfo()
的函数,该函数递归地遍历对象树,将信息插入到 HTML 表的数据单元中。由于 async
为 true,生成的
getInfo() 函数将在发送请求后立即返回,然后通过 Ajax 回调调用 showInfo()
函数。
allocMem() 函数使用 validator
属性验证用户输入。如果对于两个参数中的任何一个参数,valid()
函数返回 false,那么取消远程调用,并显示一条警告消息。由于在这里
async 为 false,gc()
函数在返回控制前会一直等待响应。init()
函数调用 getInfo() 来初始化 UI,并将同一个函数传递到
setInterval(),以便每秒钟都调用该函数刷新
Web 页面的信息。
清单 3 包含用 <js:rpc>
定制标记产生的代码,它被实现为一个名为 rpc.tag 的标记文件。每个生成的函数使用一个 XHR
对象,对象的原型可以在 MonitorPage.jsp
在其头部导入的 xhr.js 文件中找到。本文后面将提供 rpc.tag 和 xhr.js 文件的源代码。
清单 3. 生成的 JavaScript 函数
var getInfoXHR = new XHR("GET", "MonitorScript.jss", true);
function getInfo() {
var request = getInfoXHR.newRequest();
getInfoXHR.addHeader("Ajax-Call", "getInfo()");
function processResponse() {
if (getInfoXHR.isCompleted()) {
var json = eval(request.responseText);
showInfo(json, "info");
}
}
getInfoXHR.sendRequest(processResponse);
}
var allocMemXHR = new XHR("POST", "MonitorScript.jss", true);
function allocMem(size, seconds) {
if (!(valid('Size', size) && valid('Seconds', seconds)))
return;
var request = allocMemXHR.newRequest();
allocMemXHR.addHeader("Ajax-Call", "allocMem(size, seconds)");
allocMemXHR.addParam("size", size);
allocMemXHR.addParam("seconds", seconds);
function processResponse() {
if (allocMemXHR.isCompleted()) {
}
}
allocMemXHR.sendRequest(processResponse);
}
var gcXHR = new XHR("POST", "MonitorScript.jss", false);
function gc() {
var request = gcXHR.newRequest();
gcXHR.addHeader("Ajax-Call", "gc()");
function processResponse() {
if (gcXHR.isCompleted()) {
alert("Garbage Collected");
}
}
gcXHR.sendRequest(processResponse);
}
|
编写服务器端脚本
MonitorScript.jss 文件包含 3 个在服务器上执行的 JavaScript 函数。getInfo()
函数(见清单 4)使用 Java Management API 获得与类、内存和线程相关的 JVM 信息。全部数据被打包到一个对象树中,在对
Ajax 请求的响应中将以 JSON 的形式返回该对象树。getInfo()
函数不必做任何特别的事情。在接下来的小节中,您将看到如何实现 RPC 机制。
清单 4. MonitorScript.jss 的 getInfo()
函数
importClass(java.lang.management.ManagementFactory);
function getInfo() {
var classes = ManagementFactory.getClassLoadingMXBean();
var memory = ManagementFactory.getMemoryMXBean();
var heap = memory.getHeapMemoryUsage();
var nonHeap = memory.getNonHeapMemoryUsage();
var gc = ManagementFactory.getGarbageCollectorMXBeans();
var threads = ManagementFactory.getThreadMXBean();
var gcCount = 0;
var gcTime = 0;
for (var i = 0; i < gc.size(); i++) {
gcCount += gc.get(i).collectionCount;
gcTime += gc.get(i).collectionTime;
}
return {
classes: {
loaded: classes.loadedClassCount,
unloaded: classes.unloadedClassCount,
},
memory: {
heap: {
init: heap.init,
used: heap.used,
committed: heap.committed,
max: heap.max
},
nonHeap: {
init: nonHeap.init,
used: nonHeap.used,
committed: nonHeap.committed,
max: nonHeap.max
}
},
gc: {
count: gcCount,
time: gcTime
},
threads: {
live: threads.threadCount,
peak: threads.peakThreadCount
}
};
}
|
allocMem() 函数(见清单 5)创建一个
Java 线程,该 Java 线程执行一个用 JavaScript 实现的 run()
方法。代码使用 java.lang.reflect.Array
类的 newInstance() 方法创建一个
byte 数组,并调用 java.lang.Thread.sleep()。然后,数组就可以执行垃圾收集。
清单 5. MonitorScript.jss 的 allocMem()
函数
function allocMem(size, seconds) {
var runnable = new java.lang.Runnable() {
run: function() {
var array = new java.lang.reflect.Array.newInstance(
java.lang.Byte.TYPE, size);
java.lang.Thread.sleep(seconds * 1000);
}
}
var thread = new java.lang.Thread(runnable);
thread.start();
}
|
清单 6 显示 gc() 函数,该函数调用
JVM 的垃圾收集器。
清单 6. MonitorScript.jss 的 gc() 函数
function gc() {
java.lang.System.gc();
}
|
在上一小节,您看到了由 <js:rpc>
标记生成的 JavaScript 代码。为了简化生成的代码,所有与 XMLHttpRequest
API 相关的操作,例如创建新的请求对象或发送 HTTP 请求,都被放到一个单独的名为 xhr.js 的
JavaScript 文件中。本节展示 XHR
对象的方法。
初始化请求
XHR() 构造函数(见清单 7)带有 3
个参数(method、url
和 async),它将这些参数存储为属性。newRequest()
方法终止仍在活动的前一个请求,用 delete
操作符释放内存,并创建一个新的 XMLHttpRequest
实例。该行为适合使用 Ajax 连接到一个数据提要或保存用户输入的应用程序。通常情况下,您只对装载或保存最新的数据感兴趣。
清单 7. xhr.js 的 XHR() 和 newRequest()
函数
function XHR(method, url, async) {
this.method = method.toUpperCase();
this.url = url;
this.async = async;
}
XHR.prototype.newRequest = function() {
var request = this.request;
if (request) {
request.onreadystatechange = function() { };
if (request.readyState != 4)
request.abort();
delete request;
}
request = null;
if (window.ActiveXObject)
request = new ActiveXObject("Microsoft.XMLHTTP");
else if (window.XMLHttpRequest)
request = new XMLHttpRequest();
this.request = request;
this.sent = false;
this.params = new Array();
this.headers = new Array();
return request;
}
|
清单 8 显示 addParam() 和 addHeader()
方法,通过这两个方法可以添加包括在 HTTP 请求中的请求参数和头部。一旦创建新的请求后,便可以使用这些方法。如果还没有创建
XMLHttpRequest 对象,或者已经发送了请求,那么
checkRequest() 函数将抛出一个异常。
清单 8. xhr.js 的 checkRequest()、addParam()
和 addHeader() 函数
XHR.prototype.checkRequest = function() {
if (!this.request)
throw "Request not created";
if (this.sent)
throw "Request already sent";
return true;
}
XHR.prototype.addParam = function(pname, pvalue) {
this.checkRequest();
this.params[this.params.length] = { name: pname, value: pvalue };
}
XHR.prototype.addHeader = function(hname, hvalue) {
this.checkRequest();
this.headers[this.headers.length] = { name: hname, value: hvalue };
}
|
发送请求
sendRequest() 函数(见清单 9)将参数编码,打开请求,添加头部,当
async 为 true
时设置 Ajax 回调,并发送 HTTP 请求。如果 async
为 false,则在 send()
之后调用回调。
清单 9. xhr.js 的 sendRequest() 函数
XHR.prototype.sendRequest = function(callback) {
this.checkRequest();
var query = "";
for (var i = 0; i < this.params.length; i++) {
if (query.length > 0)
query += "&";
query += encodeURIComponent(this.params[i].name) + "="
+ encodeURIComponent(this.params[i].value);
}
var url = this.url;
if (this.method == "GET" && query.length > 0) {
if (url.indexOf("?") == -1)
url += "?";
else
url += "&";
url += query;
}
this.request.open(this.method, url, this.async);
for (var i = 0; i < this.headers.length; i++)
this.request.setRequestHeader(
this.headers[i].name, this.headers[i].value);
if (this.async)
this.request.onreadystatechange = callback;
var body = null;
if (this.method == "POST") {
body = query;
this.request.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
}
this.sent = true;
this.request.send(body);
if (!this.async)
callback();
}
|
在 Ajax 回调中,可以使用 isCompleted()(见清单
10)验证请求的状态。
清单 10. xhr.js 的 isCompleted() 函数
XHR.prototype.isCompleted = function() {
if (this.request && this.sent)
if (this.request.readyState == 4)
if (this.request.status == 200)
return true;
else
this.showRequestInfo();
return false;
}
|
报告错误
如果服务器端发生错误,则 isCompleted()
调用 showRequestInfo(),清单
11 显示了后者的代码。该函数打开一个窗口,并输出请求的信息:HTTP 方法、URL、参数、头部和响应。
清单 11. xhr.js 的 showRequestInfo()
函数
var xhrErrorWindow = null;
XHR.prototype.showRequestInfo = function() {
if (xhrErrorWindow && (xhrErrorWindow.closed || xhrErrorWindow._freeze))
return;
xhrErrorWindow = window.open("", "XHRError", "menubar=no, resizable=yes, "
+ "scrollbars=yes, width=600, height=600");
var doc = xhrErrorWindow.document;
doc.writeln("<p align='right'>");
doc.writeln("<button onclick='window._freeze = true'>Freeze</button>")
doc.writeln("</p>");
doc.writeln(htmlEncode(this.method + " " + this.url));
doc.writeln("<pre>" + this.request.status + "</pre>");
doc.writeln("Parameters:");
doc.writeln("<pre>");
for (var i = 0; i < this.params.length; i++) {
doc.writeln(htmlEncode(this.params[i].name
+ "=" + this.params[i].value));
}
doc.writeln("</pre>");
doc.writeln("Headers:");
doc.writeln("<pre>");
for (var i = 0; i < this.headers.length; i++) {
doc.writeln(htmlEncode(this.headers[i].name
+ "=" + this.headers[i].value));
}
doc.writeln("</pre>");
doc.writeln("Response:");
var response = this.request.responseText;
doc.writeln(response);
doc.close();
xhrErrorWindow.focus();
}
|
如果之前的 HTTP 错误已经打开了错误窗口,那么 focus()
调用将使之成为当前窗口,如果再三发生 HTTP 错误,那么会导致可用性问题。在这种情况下,由于每秒钟都会刷新窗口的内容,导致无法使用滚动,因此也难以对错误进行分析。
为了修复这些可用性问题,showRequestInfo()
函数增加了一个按钮,当单击此按钮时,该按钮设置一个名为 _freeze
的变量。如果 _freeze 为 true,则不刷新请求的信息。此外,如果用户关闭了错误窗口,该窗口将不再重新打开。在对代码做出更改之后,只需刷新应用程序的页面,就可以验证错误是否仍然存在还是已经被修复。
htmlEncode() 函数(见清单 12)以一个字符串作为参数,并分别用
&、<
和 > 替换 &、<
和 > 字符。
清单 12. xhr.js 的 htmlEncode() 函数
function htmlEncode(value) {
return value ? value.replace(/&/g, "&")
.replace(/</g, "<").replace(/>/g, ">") : "";
}
|
本节介绍生成 JavaScript 函数的 JSP 标记文件,这些 JavaScript 函数实现 RPC
机制的客户端部分。您还将看到如何调用与这些函数对应的服务器端函数,以及如何返回结果。但是首先了解一些关于安全性的注意事项。
授权函数调用
服务器端脚本可以视作常规的资源,可以使用 Java EE 的标准安全性过程限制对它们的访问。根据 web.xml
文件中指定的安全性约束,通常会定义一个或多个角色,这些角色的用户将拥有访问这些脚本的适当权限。
无论是否限制对脚本的访问,Web 客户机应该不能调用服务器端脚本的任何函数。要控制可以通过 RPC 机制调用哪些
JavaScript 函数,一种简单的方式是在一个 Set
集合中维护它们。AuthorizedCalls
bean(见清单 13)提供了一些线程安全的方法,用于管理这个经过授权的调用的集合。
清单 13. AuthorizedCalls 类
package jsee.rpc;
import java.util.*;
public class AuthorizedCalls implements java.io.Serializable {
private Set<String> calls;
public AuthorizedCalls() {
calls = new HashSet<String>();
}
protected String encode(String scriptURI, String functionName) {
return scriptURI + '#' + functionName;
}
public synchronized void add(String scriptURI, String functionName) {
calls.add(encode(scriptURI, functionName));
}
public synchronized void remove(String scriptURI, String functionName) {
calls.remove(encode(scriptURI, functionName));
}
public synchronized boolean contains(
String scriptURI, String functionName) {
return calls.contains(encode(scriptURI, functionName));
}
public String toString() {
return calls.toString();
}
}
|
本文的示例应用程序需要授权从 JSP 页面进行的调用。authorize.tag 文件(见清单 14)有两个属性(function
和 script),这两个属性的值被传递到 AuthorizedCalls
的 add() 方法。此外,任何相对的脚本 URI
被转换为绝对 URI,以确保可以用 URI 惟一地标识每个脚本。由于 AuthorizedCalls
实例存储在 session 作用域,因此在限制某些用户对脚本的访问的情况下,只能以授权用户的身份执行服务器端函数。
清单 14. authorize.tag 文件
<%@ attribute name="function" required="true" rtexprvalue="true" %>
<%@ attribute name="script" required="true" rtexprvalue="true" %>
<jsp:useBean id="authorizedCalls"
class="jsee.rpc.AuthorizedCalls" scope="session"/>
<%
String functionName = (String) jspContext.getAttribute("function");
String scriptURI = (String) jspContext.getAttribute("script");
if (!scriptURI.startsWith("/")) {
String base = request.getRequestURI();
base = base.substring(0, base.lastIndexOf("/"));
scriptURI = base + "/" + scriptURI;
}
authorizedCalls.add(scriptURI, functionName);
%>
|
另一个需要分析的与安全性相关的方面是,如何在服务器端处理远程调用函数的参数。一种常见的想法是在 Web
浏览器中将 JavaScript 对象编码为 JSON 字符串,然后将它们发送到服务器,在服务器上则可以使用
eval() 轻松地进行解码。然而,这个想法犯了一个很大的错误。这样会使恶意用户乘机注入将在服务器上执行的代码。
本文中的示例代码只允许通过 Ajax 提交的原语类型(例如字符串和数字)的参数。在服务器端,它们被视作字符串,必要时由
JavaScript 引擎自动将它们转换成数字。如果需要更复杂的类型,不应该依靠 eval()
在服务器上进行解码,而应该使用自己的编码/解码方法。
使用标记文件生成
JavaScript 代码
本文第一节提供的 MonitorPage.jsp 文件使用 <js:rpc>
标记(见清单 15)生成调用服务器端函数的 JavaScript 例程。
清单 15. 在 MonitorPage.jsp 中使用 <js:rpc>
<js:rpc function="getInfo()" script="MonitorScript.jss"
method="GET" async="true" jsonVar="json">
showInfo(json, "info");
</js:rpc>
<js:rpc function="allocMem(size, seconds)" script="MonitorScript.jss"
validator="valid('Size', size) && valid('Seconds', seconds)"
method="POST" async="true"/>
<js:rpc function="gc()" script="MonitorScript.jss"
method="POST" async="false">
alert("Garbage Collected");
</js:rpc>
|
清单 16 包含 rpc.tag 文件,该文件实现定制标记。该标记文件声明它的属性和使用的 JSP 库,用
<js:authorize> 调用
authorize.tag 文件,设置两个 JSP 变量
xhrVar 和 paramList,并生成具有给定名称和参数的客户端
JavaScript 函数。
对于在生成的 JavaScript 代码(将在 Web 浏览器中执行)中使用的 JavaScript
变量,服务器端使用一个 xhrVar 变量维护该变量的名称。xhrVar
变量的值由函数名和 XHR 字符串组成。例如,如果
function 是 getInfo(),那么这个
JSP 变量的值(以及 JavaScript 变量的名称)为
getInfoXHR。
另一个 JSP 变量 paramList 将保存通过
( 和 )
之间的 function 属性提供的参数列表。例如,如果
function 为 allocMem(size,
seconds),paramList
变量将保存 size, seconds 列表。
清单 16. rpc.tag 文件
<%@ attribute name="function" required="true" rtexprvalue="true" %>
<%@ attribute name="script" required="true" rtexprvalue="true" %>
<%@ attribute name="validator" required="false" rtexprvalue="true" %>
<%@ attribute name="jsonVar" required="false" rtexprvalue="true" %>
<%@ attribute name="method" required="false" rtexprvalue="true" %>
<%@ attribute name="async" required="true" rtexprvalue="true"
type="java.lang.Boolean" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="js" tagdir="/WEB-INF/tags/js" %>
<js:authorize script="${script}" function="${function}"/>
<c:set var="xhrVar" value="${fn:trim(fn:substringBefore(function, '('))}XHR"/>
<c:set var="paramList"
value="${fn:substringBefore(fn:substringAfter(function, '('), ')')}"/>
var ${xhrVar} = new XHR("${method}", "${script}", ${async});
function ${function} {
<c:if test="${!empty validator}">
if (!(${validator}))
return;
</c:if>
var request = ${xhrVar}.newRequest();
${xhrVar}.addHeader("Ajax-Call", "${function}");
<c:forEach var="paramName" items="${paramList}">
<c:set var="paramName" value="${fn:trim(paramName)}"/>
${xhrVar}.addParam("${paramName}", ${paramName});
</c:forEach>
function processResponse() {
if (${xhrVar}.isCompleted()) {
<c:if test="${!empty jsonVar}">
var ${jsonVar} = eval(request.responseText);
</c:if>
<jsp:doBody/>
}
}
${xhrVar}.sendRequest(processResponse);
}
|
rpc.tag 生成的 JavaScript 代码的第一行创建 XHR
对象。然后,该标记文件生成一些 JavaScript 函数,这些函数可在 Web 浏览器中用于调用相应的服务器端函数。如果
validator 属性有一个非空值,则生成的代码中会包括一个
JavaScript 表达式,以决定是否可以进行远程调用。
接下来,使用 newRequest() 函数初始化一个新的
XMLHttpRequest 对象,该对象存储在一个名为
request 的本地 JavaScript
变量中。生成的代码将添加 Ajax-Call
头部,它的值就是函数的签名。然后,将参数添加到 XHR 对象。清单 17 包含生成的 allocMem()
函数的代码。
清单 17. 生成的 allocMem() 函数
var allocMemXHR = new XHR("POST", "MonitorScript.jss", true);
function allocMem(size, seconds) {
if (!(valid('Size', size) && valid('Seconds', seconds)))
return;
var request = allocMemXHR.newRequest();
allocMemXHR.addHeader("Ajax-Call", "allocMem(size, seconds)");
allocMemXHR.addParam("size", size);
allocMemXHR.addParam("seconds", seconds);
function processResponse() {
if (allocMemXHR.isCompleted()) {
}
}
allocMemXHR.sendRequest(processResponse);
}
|
完成 XHR 对象的初始化后,rpc.tag
文件生成一个名为 processResponse()
的 Ajax 回调。该函数验证 Ajax 请求是否完成,如果提供了
jsonVar 属性,则还会计算响应。
对于 JSP 页面中在 <js:rpc>
和 </js:rpc> 之间的内容,将使用
<jsp:doBody/> 将其包括在
Ajax 回调中。例如,MonitorPage.jsp 的 <js:rpc
function="getInfo()" ...> 元素包含 showInfo(json,
"info");,用于处理 JSON 响应。清单 18 显示该代码在由
rpc.tag 生成的 getInfo() 函数中的位置。
清单 18. 生成的 getInfo() 函数
var getInfoXHR = new XHR("GET", "MonitorScript.jss", true);
function getInfo() {
var request = getInfoXHR.newRequest();
getInfoXHR.addHeader("Ajax-Call", "getInfo()");
function processResponse() {
if (getInfoXHR.isCompleted()) {
var json = eval(request.responseText);
showInfo(json, "info");
}
}
getInfoXHR.sendRequest(processResponse);
}
|
调用脚本的函数
每当在 Web 浏览器中调用一个生成的函数时,将使用 XHR
对象发送一个 URL 以 .jss 结束的 Ajax 请求。此外,对于必须在服务器端调用的函数,必须以名为
Ajax-Call 的 HTTP 头部的形式提供它的签名。.jss
请求由本系列第一部分提供的一个名为 JSServlet
的 servlet 处理。
当示例应用程序的 MonitorScript.jss 被请求时,JSServlet
实际执行 3 个脚本:init.jss、 MonitorScript.jss 和 finalize.jss。init.jss
脚本(见清单 19)获取请求参数(这些参数是 Java 字符串),将它们转换成 JavaScript 字符串,并将参数存储为
param 对象的属性。init.jss 脚本还提供用于访问和设置
bean 的函数。
清单 19. init.jss 文件
var debug = true;
var debugStartTime = java.lang.System.nanoTime();
var param = new Object();
var paramValues = new Object();
function initParams() {
var paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
var name = paramNames.nextElement();
param[name] = String(request.getParameter(name));
paramValues[name] = new Array();
var values = request.getParameterValues(name);
for (var i = 0; i < values.length; i++)
paramValues[name][i] = String(values[i]);
}
}
initParams();
function getBean(scope, id) {
return eval(scope).getAttribute(id);
}
function setBean(scope, id, bean) {
if (!bean)
bean = eval(id);
return eval(scope).setAttribute(id, bean);
}
|
由于所有 3 个脚本都在同一个上下文中执行,finalize.jss(见清单 20)可以使用 init.jss
和 MonitorScript.jss 的变量和函数。finalize.jss 脚本获取 Ajax-Call
头部,验证调用是否被授权,使用 eval()
调用脚本的函数,并使用 toSource()
将返回的对象转换成 JSON 字符串。由于函数参数是作为请求参数传输的,可以从 param
对象获得它们的值。
清单 20. finalize.jss 文件
var ajaxCall = request.getHeader("Ajax-Call");
if (ajaxCall != null) {
var authorizedCalls = getBean("session", "authorizedCalls");
if (authorizedCalls.contains(request.requestURI, ajaxCall)) {
var ajaxResponse = eval("with(param) " + ajaxCall);
if (ajaxResponse)
print(ajaxResponse.toSource());
}
}
var debugEndTime = java.lang.System.nanoTime();
if (debug)
println("// Time: " + (debugEndTime - debugStartTime) + " ns");
|
使用 eval() 执行函数是安全的,因为使用
authorizedCalls.contains() 对 Ajax-Call
头部进行了验证。
在本文中,您学习了如何在 Ajax 和同时在服务器和客户机上使用 JavaScript 代码的 Java
应用程序中使用 RPC。您还看到了如何用 JavaScript 实现 Java 接口,如何在 JavaScript
代码中创建 Java 数组和启动线程,以及如何在连接到数据提要时管理 Ajax 请求的生命周期。
| 描述 |
名字 |
大小 |
下载方法 |
本文的示例应用程序 |
jsee_part2_src.zip |
23KB |
|
|