12.3 Nashorn和javax.script包
Nashorn 不是 Java 平台提供的第一个脚本语言。早在 Java 6 中就提供了 javax.script 包,这个包为引擎提供了通用接口,让脚本语言能和 Java 相互操作。
这个通用接口中包含脚本语言的基本概念,例如脚本代码的执行和编译方式(完整的脚本或者单个脚本语句是否在现有的上下文中)。而且还提出了脚本实体和 Java 绑定的概念,以及发现脚本引擎的功能。最后,javax.script 包提供了可选的调用功能(有别于执行,因为调用功能的作用是从脚本语言的运行时中导出中间代码,提供给 JVM 运行时使用)。
本节的示例使用 Rhino 语言编写,不过也有很多其他语言利用了 javax.script 包提供的功能。Java 8 移除了 Rhino,现在 Java 平台提供的默认脚本语言是 Nashorn。
通过javax.script包使用Nashorn
我们看一个非常简单的示例,这个示例展示了如何在 Java 代码中使用Nashorn运行 JavaScript 代码:
import javax.script.*;ScriptEngineManager m = new ScriptEngineManager();ScriptEngine e = m.getEngineByName("nashorn");try {e.eval("print('Hello World!');");} catch (final ScriptException se) {// ...}
这段代码的重点是 ScriptEngine 对象,这个对象通过 ScriptEngineManager 对象获取。ScriptEngine 对象提供一个空脚本环境,然后调用 eval() 方法把代码添加到这个环境中。
Nashorn 引擎只提供了一个全局 JavaScript 对象,所以每次调用 eval() 方法都会在同一个环境中执行 JavaScript 代码。也就是说,我们可以多次调用 eval() 方法,在脚本引擎中逐渐积累 JavaScript 状态。例如:
e.eval("i = 27;");e.put("j", 15);e.eval("var z = i + j;");System.out.println(((Number) e.get("z")).intValue()); // 打印42
注意,直接在 Java 代码中与脚本引擎交互有个问题:一般不知道值的任何类型信息。
然而,Nashorn 和 Java 的类型系统绑定得相当紧密,所以要稍微小心一些。在 Java 代码中使用 JavaScript 中等价的基本类型时,往往都会转换成对应的(包装)类型。例如,如果把下面这行代码添加到前一个示例的末尾:
System.out.println(e.get("z").getClass());
很明显,e.get("z") 获得的值是 java.lang.Integer 类型。如果稍微修改一下,改成:
e.eval("i = 27.1;");e.put("j", 15);e.eval("var z = i + j;");System.out.println(e.get("z").getClass());
那么 e.get("z") 返回值的类型就变成了 java.lang.Double,由此体现了两种类型系统之间的区别。在其他 JavaScript 实现中,可能会把两种情况的返回值都当成数字类型(因为 JavaScript 没有定义整数类型)。可是,Nashorn 对数据的真正类型知道的更多。
使用 JavaScript 时,Java 程序员必须清醒地认识到,Java 的静态类型和 JavaScript 类型的动态本性之间是有区别的。如果没认识到这一点,很容易在不经意间引入缺陷。
在上述示例中,我们在 ScriptEngine 对象上调用 get() 和 put() 方法。这么做可以直接获取和设定 Nashorn 引擎当前全局作用域中的对象,无需直接编写或使用 eval() 方法执行 JavaScript 代码。
javax.script API
本节最后,我们要简介 javax.script API 中的关键类和接口。这个 API 相当小(6 个接口,5 个类,1 个异常),自从 Java 6 引入之后就没改动过。
ScriptEngineManager类
这个类是脚本功能的入口点,维护着一组当前进程中可用的脚本实现。这个功能由 Java 的服务提供者(service provider)机制实现,这种机制经常用于管理不同实现之间可能有较大差异的 Java 平台扩展。默认情况下,唯一可用的脚本扩展是 Nashorn,不过,也能使用其他脚本环境(例如 Groovy 和 JRuby)。
ScriptEngine接口
这个接口表示脚本引擎,作用是维护解释脚本的环境。
Bindings接口
这个接口扩展 Map 接口,把字符串(变量或其他符号的名称)映射到脚本对象上。Nashorn 使用这个接口实现 ScriptObjectMirror 机制,让 Java 和 JavaScript 代码能相互操作。
其实,多数应用只会使用 ScriptEngine 这个相对不透明的接口提供的方法,例如 eval()、get() 和 put() 方法,不过,理解这个接口在整个脚本 API 中的作用,会对你有所帮助。
使用 JavaScript 时,Java 程序员必须清醒地认识到,Java 的静态类型和 JavaScript 类型的动态本性之间是有区别的。如果没认识到这一点,很容易在不经意间引入缺陷。