浙江省高等学校教师教育理论培训

微信搜索“毛凌志岗前心得”小程序

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

JavaScript EE,第 2 部分: 用 Ajax 调用远程 JavaScript 函数

JavaScript EE,第 2 部分: 用 Ajax 调用远程 JavaScript 函数

JavaScript EE,第 2 部分: 用 Ajax 调用远程 JavaScript 函数

使用 Ajax 和 Java 代码实现用于 JavaScript 的 RPC

Andrei Cioroianu, 高级 Java 开发人员和顾问, Devsphere

 

简介: 在本系列的 第 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 代码。

查看本系列更多内容

 

 

发布日期: 2009 年 6 月 15 日


级别: 中级
其他语言版本: 英文


访问情况 : 3483 次浏览

评论: 0 (查看 | 添加评论 - 登录)

平均分 1 星 共 3 个评分 平均分 (3个评分)
为本文评分



RPC 机制非常简单。服务器上有一组 JavaScript 函数,现在希望能从 Web 浏览器调用它们,就像有一个 JavaScript 引擎在同时执行客户机代码和服务器代码一样。因此,需要有一些客户端例程,这些例程应该与服务器端的对应例程有相同的名称和参数。

客户端例程将使用 Ajax 把它们的参数传递到服务器,在服务器上进行真正的处理。一个 Java servlet 将调用服务器端函数,并使用 JSON 格式将结果返回到客户机。然后,客户端例程计算 Ajax 响应,将 JSON 字符串转换回 JavaScript 对象,后者被返回到应用程序。

作为应用程序开发人员,可以将精力放在构建用户界面和服务器中执行的函数上。您不需要处理 Ajax 或 RPC 问题,因为本文以标记文件的形式提供了一个 JavaScript 代码生成器,您可以在 JSP 页面使用它来自动生成客户端例程。为了理解其中的工作原理,我们从一个示例应用程序开始。

开发一个 JVM 监视应用程序

本文中的示例应用程序使用 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 响应
methodHTTP 方法,可以是 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();
}

 

 

构建 XMLHttpRequest 包装器

在上一小节,您看到了由 <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();
}

 

 

在 FireFox 中启用 window.focus()

选择 Tools > Options > Content,单击 Enable JavaScript 旁的 Advanced 按钮,并确保选择了 Advanced JavaScript Settings 中的 Rise or lower windows。

如果之前的 HTTP 错误已经打开了错误窗口,那么 focus() 调用将使之成为当前窗口,如果再三发生 HTTP 错误,那么会导致可用性问题。在这种情况下,由于每秒钟都会刷新窗口的内容,导致无法使用滚动,因此也难以对错误进行分析。

为了修复这些可用性问题,showRequestInfo() 函数增加了一个按钮,当单击此按钮时,该按钮设置一个名为 _freeze 的变量。如果 _freeze 为 true,则不刷新请求的信息。此外,如果用户关闭了错误窗口,该窗口将不再重新打开。在对代码做出更改之后,只需刷新应用程序的页面,就可以验证错误是否仍然存在还是已经被修复。

htmlEncode() 函数(见清单 12)以一个字符串作为参数,并分别用 &amp;、&lt; 和 &gt; 替换 &、< 和 > 字符。


清单 12. xhr.js 的 htmlEncode() 函数

function htmlEncode(value) {
    return value ? value.replace(/&/g, "&amp;")
        .replace(/</g, "&lt;").replace(/>/g, "&gt;") : "";
}

 

 

实现 JavaScript-RPC 机制

本节介绍生成 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 请求的生命周期。

posted on 2012-02-27 22:35  lexus  阅读(340)  评论(0)    收藏  举报