本系列文章围绕的主旨是将JavaScript与服务器上的Java代码结合起来,从而能够在服务器和客户机上使用相同的JavaScript例程。此外,本系列所展示的这些技术将让您能为Ajax 客户机和非Ajax 客户机维护同一个代码库。由于服务器端的大部分代码依然用Java 语言编写,所以有必要对 JavaScript 公开这些 Java Platform, Enterprise Edition (Java EE) 特性。在本系列中,您将了解如何在服务器端运行JavaScript文件、如何用Ajax调用远程JavaScript函数以及如何借助JavaServer Pages (JSP)技术使用这个Java Scripting API。
典型的Ajax应用程序在客户端一般都使用JavaScript,而在服务器端常常使用另外一种语言,比如 Java。因此,开发人员必须将其中一些例程实现两次,一次用于在Web 浏览器使用JavaScript,另一次用于在服务器使用另外一种语言。这种双重编码问题实际上可以通过将JavaScript 和服务器端的Java代码结合起来加以避免,而对脚本语言的完整支持可以通过javax.script API 获得。此外,Java SE Development Kit (JDK) 6 已经包含了 Mozilla的Rhino JavaScript引擎,这意味着您将无需进行任何设置。
在本系列的第一篇文章中,将使用一个简单的脚本运行程序来在一个Jave EE应用程序内执行 JavaScript文件。这些脚本将能访问被用在JSP 页面内的所谓的“隐式对象”,比如application、session、request和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对象都有的。