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

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