Java 对象转换
当脚本引擎使运行于引擎环境中的脚本能够使用 Java 对象时,引擎需要将其封装到适用于该脚本语言的对象类型中。封装可能会涉及到一些适当的对象-值转换,比如说允许 Java Integer 对象直接在脚本语言的数学表达式中使用。关于如何将 Java 对象转换为脚本对象的研究是与各个脚本语言的引擎特别相关的,并且不在本文的讨论范围之内。但是,您应该意识到转换的发生,因为可以通过测试来确保所使用的脚本语言执行转换的方式符合您的期望。
ScriptEngine.put 及其相关 get() 方法是在运行于脚本引擎中的 Java 代码和脚本之间共享对象和数据的主要途径。(有关这一方面的详细论述,请参阅本文后面的 Script-execution scope 一节。)当我们调用引擎的 put() 方法时,脚本引擎会将第二个参数(任何 Java 对象)关联到特定的字符串关键字。大多数脚本引擎都是让脚本使用特定的变量名称来访问 Java 对象。脚本引擎可以随意对待传递给 put() 方法的名称。比如说,JRuby 脚本引擎让 Ruby 代码使用全局 $helloScriptingWorld 对象访问 helloScriptingWorld,以符合 Ruby 全局变量的语法。
脚本引擎的 get() 方法检索脚本环境中可用的值。一般而言,Java 代码通过 get() 方法可以访问脚本环境中的所有全局变量和函数。但是只有明确使用 put() 与脚本共享的 Java 对象才可以被脚本访问。
外部脚本在运行着的应用程序中访问和操作 Java 对象的这种功能是扩展 Java 程序功能的一项强有力的技巧。(第 2 部分将通过示例研究这一技巧)。
运行 HelloScriptingWorld 应用程序
您可以通过下载和构建源代码来运行 HelloScriptingWorld 应用程序。此 .zip 中文件含有一个 Ant 脚本和一个 Maven 构建脚本,可以帮助大家编译和运行示例应用程序。请执行以下步骤:
·下载 此 .zip 文件。
·创建一个新目录,比如说 java-scripting,并将步骤 1 中所下载的文件解压到该目录中。
·打开命令行 shell 并转到该目录。
·运行 ant run-hello 命令。
您应该可以看到类似于清单 6 的 Ant 控制台输出。注意,defineScriptFunction() 函数没有产生任何输出,因为它虽然定义了输出但是却没有调用 JavaScript 函数。
清单 6. 运行 HelloScriptingWorld 时的输出
| Calling invokeHelloScript... Hello from JavaScript Calling defineScriptFunction... Calling invokeScriptFunctionFromEngine... Hello, World! Calling invokeScriptFunctionFromJava... Hello, from Java Calling invokeJavaFromScriptFunction... Invoking getHelloReply method from JavaScript... Java returned: Java method getHelloReply says, 'Hello, JavaScript' |
Java 5 兼容性
Java SE 6 引入了 Java 脚本 API,但是您也可以使用 Java SE 5 运行此 API。只需要提供缺少的 javax.script 包类的一个实现即可。所幸的是,Java Specification Request 223 参考实现中含有这个实现(请参阅 参考资料 获得下载链接。)JSR 223 对 Java 脚本 API 做出了定义。
如果您已经下载了 JSR 223 参考实现,解压下载文件并将 script-api.jar、script-js.jar 和 js.jar 文件复制到您的类路径下。这些文件将提供脚本 API、JavaScript 脚本引擎接口和 Java SE 6 中所附带的 JavaScript 脚本引擎。
脚本执行作用域
与简单地调用引擎的 get() 和 put() 方法相比,如何将 Java 对象公开给运行于脚本引擎中的脚本具有更好的可配置性。当我们在脚本引擎上调用 get() 或 put() 方法时,引擎将会在 javax.script.Bindings 接口的默认实例中检索或保存所请求的关键字。(Bindings 接口只是一个 Map 接口,用于强制关键字为字符串。)
当代码调用脚本引擎的 eval() 方法时,将使用引擎默认绑定的关键字和值。但是,您可以为 eval() 调用提供自己的 Bindings 对象,以限制哪些变量和对象对于该特定脚本可见。该调用外表上类似于 eval(String, Bindings) 或 eval(Reader, Bindings)。要帮助您创建自定义的 Bindings,脚本引擎将提供一个 createBindings() 方法,该方法和返回值是一个内容为空的 Bindings 对象。使用 Bindings 对象临时调用 eval 将隐藏先前保存在引擎默认绑定中的 Java 对象。
要添加功能,脚本引擎含有两个默认绑定:其一为 get() 和 put() 调用所使用的 “引擎作用域” 绑定 ;其二为 “全局作用域” 绑定,当无法在 “引擎作用域” 中找到对象时,引擎将使用第二种绑定进行查找。脚本引擎并不需要使脚本能够访问全局绑定。大多数脚本都可以访问它。
“全局作用域” 绑定的设计目的是在不同的脚本引擎之间共享对象。ScriptEngineManager 实例返回的所有脚本引擎都是 “全局作用域” 绑定对象。您可以使用 getBindings(ScriptContext.GLOBAL_SCOPE) 方法检索某个引擎的全局绑定,并且可以使用 setBindings(Bindings, ScriptContext.GLOBAL_SCOPE) 方法为引擎设置全局绑定。
ScriptContext 是一个定义和控制脚本引擎运行时上下文的接口。脚本引擎的 ScriptContext 含有 “引擎” 和 “全局” 作用域绑定,以及用于标准输入和输出操作的输入和输出流。您可以使用引擎的 getContext() 方法获取并操作脚本引擎的上下文。
一些脚本 API 概念,比如说作用域、绑定 和上下文,开始看来会令人迷惑,因为它们的含义有交叉的地方。本文的源代码下载文件含有一个名为 ScriptApiRhinoTest 的 JUnit 测试文件,位于 src/test/java directory 目录,该文件可以通过 Java 代码帮助解释这些概念。
未来的计划
现在,大家已经对 Java 脚本 API 有了最基本的认识,本系列文章的第 2 部分将在此基础上进行扩展,为大家演示一个更为实际的示例应用程序。该应用程序将使用 Groovy、Ruby 和 JavaScript 一起编写的外部脚本文件来定义可在运行时修改的业务逻辑。如您如见,在脚本语言中定义业务规则可以使规则的编写更加轻松,并且更易于程序员之外的人员阅读,比如说业务分析师或规则编写人员。