JavaScript EE,第 1 部分: 在服务器端运行 JavaScript 文件

JavaScript EE,第 1 部分: 在服务器端运行 JavaScript 文件

JavaScript EE,第 1 部分: 在服务器端运行 JavaScript 文件

了解如何在 Ajax 和 Java EE 应用程序内使用 javax.script API

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

简介: 本系列文章围绕的主旨是将 JavaScript 与服务器上的 Java™ 代码结合起来,从而能够在服务器和客户机上使用相同的 JavaScript 例程。此外,本系列所展示的这些技术将让您能为 Ajax 客户机和非 Ajax 客户机
维护同一个代码库。由于服务器端的大部分代码依然用 Java 语言编写,所以有必要对 JavaScript 公开这些 Java Platform, Enterprise Edition (Java EE) 特性。在本系列中,您将了解如何在服务器端运行 JavaScript 文件、如何用 Ajax 调用远程 JavaScript
函数以及如何借助 JavaServer Pages
(JSP) 技术使用这个 Java Scripting API。

查看本系列更多内容

本文的标签:  js

 

发布日期: 2009 年 1 月 12 日


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


访问情况 : 5908 次浏览

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

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



典型的 Ajax 应用程序在客户端一般都使用
JavaScript,而在服务器端常常使用另外一种语言,比如 Java。因此,开发人员必须将其中一些例程实现两次,一次用于在 Web 浏览器使用 JavaScript,另一次用于在服务器使用另外一种语言。这种双重编码问题实际上可以通过将 JavaScript 和服务器端的 Java 代码结合起来加以避免,而对脚本语言的完整支持可以通过 javax.script API 获得。此外,Java SE Development Kit
(JDK) 6 已经包含了 Mozilla 的 Rhino
JavaScript 引擎,这意味着您将无需进行任何设置。

在本系列的第一篇文章中,将使用一个简单的脚本运行程序来在一个 Jave EE 应用程序内执行 JavaScript
文件。这些脚本将能访问被用在 JSP 页面内的所谓的 “隐式对象”,比如 applicationsessionrequestresponse。本文中的大多数示例均包含可重用代码,这样一来,您可以在自己的应用程序中轻松地将 JavaScript 应用于服务器上。

使用 javax.script API

本节给出了 javax.script API 的概览,展示了如何执行脚本来访问 Java 对象、如何从 Java 代码调用 JavaScript 函数,以及如何为所编译的脚本实现缓存机制。

执行脚本

javax.script API 十分简单。可以先创建一个 ScriptEngineManager 实例,有了这个实例就能用下列方法中的任一个来获得 ScriptEngine 对象(参见清单 1):

  • getEngineByName()
  • getEngineByExtension()
  • getEngineByMimeType()


清单 1. 获得一个 ScriptEngine 实例

import javax.script.*;
...
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
...
engine.eval(...);

此外,还可以通过 getEngineFactories() 获得可用脚本引擎的列表。目前,只有 JavaScript 引擎是与 JDK 6 捆绑的,不过 ScriptEngineManager 实现了一种发现机制,能发现支持 JSR-223 Scripting for the Java Platform 的第三方引擎(参见 参考资料)。只需将脚本引擎的 JAR 文件放入 CLASSPATH 即可。

获得了 javax.script.ScriptEngine 实例后,就可以调用 eval() 来执行脚本了。也可以将 Java 对象作为脚本变量导出,其间要将 Bindings 实例传递给 eval() 方法。清单 2 所示的 ScriptDemo.java 示例导出两个名为 demoVar
strBuf 的变量、执行 DemoScript.js 脚本,然后让这些变量输出它们修改后的值。


清单 2. ScriptDemo.java 示例

package jsee.demo;

import javax.script.*;
import java.io.*;

public class ScriptDemo {

    public static void main(String args[]) throws Exception {
        // Get the JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // Set JavaScript variables
        Bindings vars = new SimpleBindings();
        vars.put("demoVar", "value set in ScriptDemo.java");
        vars.put("strBuf", new StringBuffer("string buffer"));

        // Run DemoScript.js
        Reader scriptReader = new InputStreamReader(
            ScriptDemo.class.getResourceAsStream("DemoScript.js"));
        try {
            engine.eval(scriptReader, vars);
        } finally {
            scriptReader.close();
        }

        // Get JavaScript variables
        Object demoVar = vars.get("demoVar");
        System.out.println("[Java] demoVar: " + demoVar);
        System.out.println("    Java object: " + demoVar.getClass().getName());
        System.out.println();
        Object strBuf = vars.get("strBuf");
        System.out.println("[Java] strBuf: " + strBuf);
        System.out.println("    Java object: " + strBuf.getClass().getName());
        System.out.println();
        Object newVar = vars.get("newVar");
        System.out.println("[Java] newVar: " + newVar);
        System.out.println("    Java object: " + newVar.getClass().getName());
        System.out.println();
    }

}

DemoScript.js 文件(如清单 3 所示)包含一个 printType() 函数,该函数可用来输出每个脚本变量的类型。这个示例会调用 strBuf 对象的 append() 方法、修改 demoVar 的值并设置一个名为 newVar 的新变量脚本。

如果传递给 printType() 的对象具有 getClass() 方法,那么它一定是个 Java 对象,该对象的类名由 obj.getClass().name 获得。这个 JavaScript 表达式调用此对象的 java.lang.Class 实例的 getName() 方法。如果此对象不具备 getClass,那么 printType()
就会调用 toSource() 方法,而该方法是所有 JavaScript 对象都有的。


清单 3. DemoScript.js 示例

println("Start script \r\n");

// Output the type of an object
function printType(obj) {
    if (obj.getClass)
        println("    Java object: " + obj.getClass().name);
    else
        println("    JS object: " + obj.toSource());
    println("");
}

// Print variable
println("[JS] demoVar: " + demoVar);
printType(demoVar);

// Call method of Java object
strBuf.append(" used in DemoScript.js");
println("[JS] strBuf: " + strBuf);
printType(strBuf);

// Modify variable
demoVar = "value set in DemoScript.js";
println("[JS] demoVar: " + demoVar);
printType(demoVar);

// Set a new variable
var newVar = { x: 1, y: { u: 2, v: 3 } }
println("[JS] newVar: " + newVar);
printType(newVar);

println("End script \r\n");

清单 4 是 ScriptDemo.java 示例的输出。值得注意的是 demoVar 作为 JavaScript
String 导出,而 strBuf 的类型仍然是 java.lang.StringBuffer。原始变量和 Java 字符串均作为本地 JavaScript 对象导出。任何其他的 Java 对象(包括数组)均原样导出。


清单 4. ScriptDemo.java 的输出

Start script

[JS] demoVar: value set in ScriptDemo.java
    JS object: (new String("value set in ScriptDemo.java"))

[JS] strBuf: string buffer used in DemoScript.js
    Java object: java.lang.StringBuffer

[JS] demoVar: value set in DemoScript.js
    JS object: (new String("value set in DemoScript.js"))

[JS] newVar: [object Object]
    JS object: ({x:1, y:{u:2, v:3}})

End script

[Java] demoVar: value set in DemoScript.js
    Java object: java.lang.String

[Java] strBuf: string buffer used in DemoScript.js
    Java object: java.lang.StringBuffer

[Java] newVar: [object Object]
    Java object: sun.org.mozilla.javascript.internal.NativeObject

运行该脚本后,此引擎就会接受所有变量(包括新变量)并执行反转变换,将 JavaScript 原始变量和字符串转变成 Java 对象。其他的 JavaScript 对象则被包装成 Java 对象,这些对象能使用某种特定于引擎的内部 API,比如 sun.org.mozilla.javascript.internal.NativeObject

有时,可能会只想使用那些标准的 API,因此 Java 代码和所执行脚本间的全部数据转换都应通过原始变量、字符串和 Java 对象(比如 bean)完成,这是因为在
JavaScript 代码内可以很容易地访问到它们的属性和方法。简言之,就是不要试图在 Java 代码内访问本地 JavaScript 对象,相反,应该在 JavaScript 代码内使用 Java 对象。

调用函数

在之前的例子中,您已经看到了从 JavaScript 调用 Java 方法是可行的。现在您将会了解如何从 Java 代码调用 JavaScript 函数。首先,必须先执行包含想要调用的那个函数的脚本。然后,再将 ScriptEngine 实例强制转换为 javax.script.Invocable,后者还提供了 invokeFunction()invokeMethod()。如果脚本实现了 Java 接口的全部方法,那么也可以使用 getInterface() 获得一个
Java 对象,该对象的方法用此脚本语言编码。

InvDemo.java 示例(如清单 5 所示)执行一个名为 InvScript.js 的脚本,它包含 demoFunction() 例程。在进行强制类型转换以将 ScriptEngine 实例转换为 Invocable 之后,这个 Java 示例才能将函数名和参数传递给引擎的 invokeFunction() 方法,而此方法会返回由 demoFunction() 返回的值。


清单 5. InvDemo.java 示例

package jsee.demo;

import javax.script.*;
import java.io.*;

public class InvDemo {

    public static void main(String args[]) throws Exception {
        // Get the JavaScript engine
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // Run InvScript.js
        Reader scriptReader = new InputStreamReader(
            InvDemo.class.getResourceAsStream("InvScript.js"));
        try {
            engine.eval(scriptReader);
        } finally {
            scriptReader.close();
        }

        // Invoke a JavaScript function
        if (engine instanceof Invocable) {
            Invocable invEngine = (Invocable) engine;
            Object result = invEngine.invokeFunction("demoFunction", 1, 2.3);
            System.out.println("[Java] result: " + result);
            System.out.println("    Java object: "
                    + result.getClass().getName());
            System.out.println();
        } else
            System.out.println("NOT Invocable");
    }

}

InvScript.js 文件(如清单 6 所示)包含 demoFunction() 例程和之前的脚本示例所用的相同 printType() 函数。


清单 6. InvScript.js 示例

println("Start script \r\n");

function printType(obj) {
    if (obj.getClass)
        println("    Java object: " + obj.getClass().name);
    else
        println("    JS object: " + obj.toSource());
    println("");
}

function demoFunction(a, b) {
    println("[JS] a: " + a);
    printType(a);
    println("[JS] b: " + b);
    printType(b);
    var c = a + b;
    println("[JS] c: " + c);
    printType(c);
    return c;
}

println("End script \r\n");

InvDemo.java 的输出如清单 7 所示,注意到其中的数值参数均被转换成了 JavaScript 对象,并且由
demoFunction() 返回的值是作为 Java 对象获得的。这种转换只会针对原始变量和字符串进行。任何其他的对象在 JVM 和 Javascript 引擎之间都是原样传递的,反之亦然。


清单 7. InvDemo.java 的输出

Start script

End script

[JS] a: 1
    JS object: (new Number(1))

[JS] b: 2.3
    JS object: (new Number(2.3))

[JS] c: 3.3
    JS object: (new Number(3.3))

[Java] result: 3.3
    Java object: java.lang.Double

请注意 javax.script.Invocable 是一个可选接口,有些脚本引擎可能不会实现该接口。不过,JDK 6 所带的 JavaScript 引擎提供对该接口的支持。

编译脚本

脚本在每次执行时都进行解析会浪费 CPU 资源。在多次执行相同的脚本时,若能编译脚本,就可以显著减少执行时间,而脚本编译所需要的方法可由另外一个可选接口 javax.script.Compilable 提供,JDK 6 所带的 JavaScript 引擎亦支持该接口。

CachedScript 类(参见清单 8)接受一个脚本文件并只有当源代码有修改时才会进行重编译。getCompiledScript() 方法会调用此脚本引擎的 compile(),进而返回 javax.script.CompiledScript 对象,该对象的 eval() 方法会执行脚本。


清单 8. CachedScript 类

package jsee.cache;

import javax.script.*;
import java.io.*;
import java.util.*;

public class CachedScript {
    private Compilable scriptEngine;
    private File scriptFile;
    private CompiledScript compiledScript;
    private Date compiledDate;

    public CachedScript(Compilable scriptEngine, File scriptFile) {
        this.scriptEngine = scriptEngine;
        this.scriptFile = scriptFile;
    }

    public CompiledScript getCompiledScript()
            throws ScriptException, IOException {
        Date scriptDate = new Date(scriptFile.lastModified());
        if (compiledDate == null || scriptDate.after(compiledDate)) {
            Reader reader = new FileReader(scriptFile);
            try {
                compiledScript = scriptEngine.compile(reader);
                compiledDate = scriptDate;
            } finally {
                reader.close();
            }
        }
        return compiledScript;
    }

}

ScriptCache 类(参见清单 9)使用 java.util.LinkedHashMap 对象为所编译的脚本实现存储库。map 的初始容量被设为所缓存脚本的最大数量并且加载系数是 1。这两个参数就确保了该 cacheMap 不需要重新处理。

默认地,LinkedHashMap 类会使用条目的插入顺序。若不想使用默认顺序,LinkedHashMap() 构造函数的第三个参数必须是 true 以便使用条目的访问顺序。

达到缓存的最大容量后,removeEldestEntry() 方法就会开始返回 true,以便当每次向此缓存添加一个新的编译了的脚本时,一个条目都会自动从 cacheMap 删除。

通过联合使用 LinkedHashMap 的自动删除机制和访问顺序,ScriptCache 就确保了当添加了新脚本时,最近最少使用的(Least Recently Used,LRU)的脚本将能够从缓存中删除。


清单 9. ScriptCache 类

package jsee.cache;

import javax.script.*;

import java.io.*;
import java.util.*;

public abstract class ScriptCache {
    public static final String ENGINE_NAME = "JavaScript";
    private Compilable scriptEngine;
    private LinkedHashMap<String, CachedScript> cacheMap;

    public ScriptCache(final int maxCachedScripts) {
        ScriptEngineManager manager = new ScriptEngineManager();
        scriptEngine = (Compilable) manager.getEngineByName(ENGINE_NAME);
        cacheMap = new LinkedHashMap<String, CachedScript>(
                maxCachedScripts, 1, true) {
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > maxCachedScripts;
            }
        };
    }

    public abstract File getScriptFile(String key);

    public synchronized CompiledScript getScript(String key)
            throws ScriptException, IOException {
        CachedScript script = cacheMap.get(key);
        if (script == null) {
            script = new CachedScript(scriptEngine, getScriptFile(key));
            cacheMap.put(key, script);
        }
        return script.getCompiledScript();
    }

    public ScriptEngine getEngine() {
        return (ScriptEngine) scriptEngine;
    }

}

下一节将使用 ScriptCache 类、实现抽象的 getScriptFile() 方法并使用 getScript() 从缓存检索所编译的脚本。

构建一个脚本运行程序

在本节中,您将了解如何创建一个简单的 Java servlet
来实现 URL-脚本的映射以便能够从 Web 浏览器调用服务器端脚本。此外,servlet 还将会把几个 Java EE 对象公开为可在 JavaScript 代码内使用的变量。您还将了解如何使用脚本上下文来用单一一个 JavaScript 引擎运行多个并发的脚本。

初始化 servlet

servlet 类的名称是 JSServlet。其 init() 方法(参见清单 10)会获得几个配置参数并创建一个 ScriptCache 对象。servlet 的脚本缓存使用 getRealPath() 获得与给定 URI 相映射的脚本文件的路径。


清单 10. JSServlet 的 init() 方法

package jsee.servlet;

import javax.script.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import jsee.cache.*;

public class JSServlet extends HttpServlet {
    private String cacheControlHeader;
    private String contentTypeHeader;
    private ScriptCache scriptCache;

    public void init() throws ServletException {
        ServletConfig config = getServletConfig();
        cacheControlHeader = config.getInitParameter("Cache-Control");
        contentTypeHeader = config.getInitParameter("Content-Type");
        int maxCachedScripts = Integer.parseInt(
                config.getInitParameter("Max-Cached-Scripts"));
        scriptCache = new ScriptCache(maxCachedScripts) {
            public File getScriptFile(String uri) {
                return new File(getServletContext().getRealPath(uri));
            }
        };
    }
    ...
}

清单
11 中包含一些 servlet 的参数,这些参数在 web.xml 文件内指定。Cache-Control 头与脚本缓存毫无关系。两个头都是由 servlet 返回的此 HTTP 响应的一部分。no-cache 值告知浏览器不要缓存此 servlet 的响应,该响应应被作为 text/plain 对待。


清单 11. web.xml 文件

<web-app ...>

    <servlet>
        <servlet-name>JSServlet</servlet-name>
        <servlet-class>jsee.servlet.JSServlet</servlet-class>
        <init-param>
            <param-name>Cache-Control</param-name>
            <param-value>no-cache</param-value>
        </init-param>
        <init-param>
            <param-name>Content-Type</param-name>
            <param-value>text/plain</param-value>
        </init-param>
        <init-param>
            <param-name>Max-Cached-Scripts</param-name>
            <param-value>1000</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>JSServlet</servlet-name>
        <url-pattern>*.jss</url-pattern>
    </servlet-mapping>

</web-app>

从清单 11 可以看出,*.jss 模式被映射给此 servlet。这意味着 JSServlet 将会处理 URL 以 .jss 扩展名结束的所有请求。当用户在 Web 浏览器内输入这样的一个 URL 或是单击了一个 .jss 链接时,浏览器就会发送此 HTTP 请求给 Web 服务器(例如, Apache),而此服务器应该被配置成将该请求分派给 servlet 容器(比如,Tomcat)。如果 servlet 容器也充当 Web 服务器,就无需额外的配置。

当 servlet 容器获得了 URL 以 .jss 结束的这个请求时,就会调用 service() 方法,该方法是由 JSServlet 继承自 javax.servlet.http.HttpServlet 的。此方法再进而调用 doGet()doPost(),最终调用哪一个取决于此请求的 HTTP 方法。两个方法都可由 JSServlet 覆盖,这一点在本节稍后的部分还会看到。

使用脚本上下文

脚本引擎的线程模型

JSR-223 Scripting for the Java Platform(参见 参考资料)定义了三类脚本引擎:

  • 能执行并发脚本的多线程引擎,可以修改由其他线程看到的变量
  • 线程隔离引擎,也是多线程的,但每个线程都具有其自身的引擎范围来保存变量
  • 无状态引擎,作为线程隔离引擎定义,但引擎范围在任何脚本执行后均保持不变

脚本引擎的类型可通过 engine.getFactory().getParameter("THREADING") 获得,而返回的结果可能会是
"MULTITHREADED""THREAD-ISOLATED""STATELESS"

每个脚本引擎实例都具有一个默认的上下文,在其中,可以用 put() 方法存储变量,而所执行的脚本的输出则被默认定向到 System.out。在服务器环境内,常希望运行具有各自上下文的并发脚本。javax.script API 能满足这个需求,它能提供 ScriptContext 接口和 SimpleScriptContext 实现。

Mozilla 的 Rhino
JavaScript 引擎是个多线程引擎(参见侧栏),允许执行共享相同上下文的并发线程。不过,在本例中,我们想要隔离这些引擎范围以及运行在不同线程内的那些脚本的输出,这意味着必须要针对每个 HTTP 请求创建一个新的 ScriptContext 实例。

清单 12 给出了 JSServlet 类的 createScriptContext() 方法。此方法设置了上下文的 writer 属性以便在脚本执行时将脚本的输出发送给 response 对象的编写者。这意味着传递给脚本内的 print()println() 的任何东西都将会包含在此 servlet 的响应内。

此外,createScriptContext() 还通过脚本上下文的 setAttribute() 方法定义了如下的脚本变量:


表 1. 由 JSServlet 执行的脚本内的可用变量

脚本变量 描述
configservlet 的 javax.servlet.ServletConfig 实例
applicationWeb 应用程序的 javax.servlet.ServletContext 实例
sessionjavax.servlet.http.HttpSession 对象
requestjavax.servlet.http.HttpServletRequest 对象
responsejavax.servlet.http.HttpServletResponse
对象
out用于输出响应的 java.io.PrintWriter 对象
factory脚本引擎的 javax.script.ScriptEngineFactory

factory 变量可用来获得有关 JavaScript 引擎的信息,比如语言版本或引擎版本。其余的变量的作用与它们在 JSP 页面上的无异。


清单 12. JSServlet 的 createScriptContext() 方法

public class JSServlet extends HttpServlet {
    ...
    protected ScriptContext createScriptContext(
            HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        ScriptContext scriptContext = new SimpleScriptContext();
        scriptContext.setWriter(response.getWriter());
        int scope = ScriptContext.ENGINE_SCOPE;
        scriptContext.setAttribute("config", getServletConfig(), scope);
        scriptContext.setAttribute("application", getServletContext(), scope);
        scriptContext.setAttribute("session", request.getSession(), scope);
        scriptContext.setAttribute("request", request, scope);
        scriptContext.setAttribute("response", response, scope);
        scriptContext.setAttribute("out", response.getWriter(), scope);
        scriptContext.setAttribute("factory",
                scriptCache.getEngine().getFactory(), scope);
        return scriptContext;
    }
    ...
}

runScript() 方法(参见清单 13)从缓存获得一个编译后的脚本并调用 eval() 方法,将给定的脚本上下文作为参数传递。


清单 13. JSServlet 的 runScript() 方法

public class JSServlet extends HttpServlet {
    ...
    protected void runScript(String uri, ScriptContext scriptContext)
            throws ScriptException, IOException {
        scriptCache.getScript(uri).eval(scriptContext);
    }
    ...
}

处理请求

可以通过调用上述的 runScript() 方法来执行
与此 HTTP 请求的 URL 相映射的那个脚本。不过,在实际的应用程序中,可能需要在运行脚本之前进行初始化并在脚本执行完后进行最后的清理工作。

在同一个上下文中,可以连续运行多个脚本。例如,一个脚本可以定义一组变量和函数,而另一个脚本则可以使用之前在相同上下文内执行的脚本的变量和函数进行一些处理。

servlet 的 handleRequest() 方法(如清单 14 所示)可设置这些 HTTP 报头、运行 init.jss 脚本、从请求的 URI 中删除此上下文路径、执行具有所获得的 URI 的脚本,然后运行另一个名为 finalize.jss 的脚本。


清单 14. JSServlet 的 handleRequest() 方法

public class JSServlet extends HttpServlet {
    ...
    protected void handleRequest(
            HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        if (cacheControlHeader != null)
            response.setHeader("Cache-Control", cacheControlHeader);
        if (contentTypeHeader != null)
            response.setContentType(contentTypeHeader);
        ScriptContext scriptContext = createScriptContext(request, response);
        try {
            runScript("/init.jss", scriptContext);
            String uri = request.getRequestURI();
            uri = uri.substring(request.getContextPath().length());
            try {
                runScript(uri, scriptContext);
            } catch (FileNotFoundException x) {
                response.sendError(404, request.getRequestURI());
            }
            runScript("/finalize.jss", scriptContext);
        } catch (ScriptException x) {
            x.printStackTrace(response.getWriter());
            throw new ServletException(x);
        }
    }
    ...
}

JSServlet
doGet()doPost() 方法(参见清单 15)用来调用 handleRequest()


清单 15. JSServletdo 的 Get() 和 doPost() 方法

public class JSServlet extends HttpServlet {
    ...
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        handleRequest(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        handleRequest(request, response);
    }

}

开发服务器端脚本

本节包含了服务器端脚本的几个例子,向您展示了如何获得请求参数、访问 JavaBeans 的属性并生成 JSON 响应。

预处理和后处理

在前一节中,曾提及在执行所请求的脚本之前,JSServlet 会调用
init.jss(如清单 16 所示)。若想估量执行脚本所需时间,可以将开始时间存储到一个变量内,如下所示。


清单 16. init.jss 脚本

var debug = true;
var debugStartTime = java.lang.System.nanoTime();

之后,可以在 finalize.jss 内(参见清单 17)计算执行时间。该时间作为 JavaScript 注释打印以便 JSServlet 能够生成有效的 JSON 响应。


清单 17. finalize.jss 脚本

var debugEndTime = java.lang.System.nanoTime();
if (debug)
    println("// Time: " + (debugEndTime - debugStartTime) + " ns");

本系列后面的文章将向 init.jss 和 finalize.jss 添加更多的代码。

获得请求参数

借助 JSServlet 调用的脚本可以通过 request.getParameter()request.getParameterValues() 访问请求参数,这二者会返回 Java 对象。若想使语法更简短或处理 JavaScript 对象而非 Java 字符串和数组,也不难,只需将下面这些代码行加入到 init.jss(参见清单 18)。


清单 18. 在 init.jss 内获得请求参数。

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();

假设您使用清单 19 所示的 URL 请求一个名为 ParamDemo.jss 的脚本。


清单 19. 请求一个脚本的 URL 示例

http://localhost:8080/jsee/ParamDemo.jss?firstName=John&lastName=Smith


ParamDemo.jss(参见清单 20)内,用 param.firstNameparam.lastName 可以得到这两个请求参数。


清单 20. ParamDemo.jss 示例

println("Hello " + param.firstName + " " + param.lastName);

访问
JavaBean

Web 应用程序的 applicationsessionrequest 范围可存储 bean 实例,可以分别使用 ServletContext
HttpSessionHttpServletRequestgetAttribute()setAttribute() 来获得或替代这些实例。也可以使用 getBean()setBean() 函数(如清单 21 所示),但这两个函数必须位于 init.jss 文件内以便任何脚本均可调用它们。scope 参数应是如下字符串中的一个:

  • "application"
  • "session"
  • "request"


清单 21. init.jss 的 getBean() 和 setBean() 函数

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);
}

现在,假设您想要在 session 范围内保存 DemoBean(参见清单 22)的一个实例。


清单 22. DemoBean.java 示例

package jsee.demo;

public class DemoBean {
    private int counter;

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

}

BeanDemo.jss 脚本(如清单 23 所示)用 importPackage(Packages.jsee.demo) 导入了包含此 JavaBean 的那个包。之后,脚本试图用 getBean()session 范围获得这个 bean 实例。如果这个 bean 没有找到,那么 BeanDemo.jss 就会创建一个对象并利用 setBean() 将其放入 session 范围。最终,此脚本会进行增量处理并输出这个 bean 的 counter 属性。


清单 23. BeanDemo.jss 示例

importPackage(Packages.jsee.demo);
var bean = getBean("session", "demo");
if (!bean) {
    bean = new DemoBean();
    setBean("session", "demo", bean);
}
bean.counter++;
println(bean.counter);

如果所要导入的包是以 javajavaxorgeducomnet 开头的,那么无须在 importPackage() 使用 Packages 前缀。此外,还可以使用 importClass() 导入单个类。

返回 JSON 响应

清单 24 所示的示例会生成一个 JSON 响应,该响应会包含有关 JavaScript 引擎和此脚本 URI 的某些信息。此示例使用 JavaScript 语法来创建 json 对象,其源代码则用 toSource() 方法以 String 形式获得。


清单 24. JsonDemo.jss 示例

var json = {
    engine: {
        name: String(factory.engineName),
        version: String(factory.engineVersion),
        threading: String(factory.getParameter("THREADING"))
    },
    language: {
        name: String(factory.languageName),
        version: String(factory.languageVersion)
    },
    script: {
        uri: String(request.requestURI)
    }
};

println(json.toSource());

在本例中,从 factoryrequest 的属性中检索到的所有 Java 对象都必须转变为 JavaScript 对象,以便 toSource() 能够正常工作。清单 25 包含了此脚本的输出:


清单 25. JsonDemo.jss 的输出

({language:{name:"ECMAScript", version:"1.6"},
engine:{name:"Mozilla Rhino", threading:"MULTITHREADED",
version:"1.6 release 2"}, script:{uri:"/jsee/JsonDemo.jss"}})

结束语

在本文中,您了解了如何使用 javax.script API 编译和执行 JavaScript 文件。您还了解了如何基于 java.util.LinkedHashMap 实现 LRU 缓存、如何将 Java 对象作为脚本变量导出、如何实现 URL-脚本映射以及如何构建在服务器端执行 的 JavaScript 文件。请您继续关注本系列的下一篇文章,在该文章中,您将了解如何用 Ajax 调用远程 JavaScript 函数。

原文地址:https://www.cnblogs.com/lexus/p/2370532.html