java 脚本api_Java脚本API简介

本文是关于Java脚本API的介绍,它允许Java应用程序与多种脚本语言进行集成,提供了统一的调用接口。文章通过HelloScriptingWorld应用程序演示了如何使用ScriptEngineManager和ScriptEngine进行脚本的调用和执行,展示了脚本API的双向可见性,允许脚本调用Java对象并动态改变应用行为。此外,文章还探讨了脚本执行范围、Java 5兼容性和API的潜在用途,如动态规则引擎的构建。
摘要由CSDN通过智能技术生成

Java开发人员知道Java语言并不总是每种任务的最佳语言。 今年的JRuby和Groovy的1.0版本发行了对向Java应用程序添加动态语言的兴趣。 借助Groovy,JRuby,Rhino,Jython和其他开源项目,可以使用所谓的脚本语言编写代码并在JVM中运行它(请参阅参考资料 )。 然而,将这些语言与Java代码集成通常意味着学习每个解释器的独特API和功能。

添加到Java SE 6的javax.script包使集成动态语言更加容易。 它提供了一种简单的方法,即使用一小组接口和具体类来调用多种脚本语言。 但是,Java脚本API不仅能使编写应用程序的各个部分的脚本变得容易,而且还具有更多的功能。 脚本包允许您在运行时读取和调用外部脚本,这意味着您可以动态更改这些脚本以更改正在运行的应用程序的行为。

本文是由两部分组成的系列文章的第一篇,介绍了使用Hello World风格的应用程序的Java脚本API的功能和键类。 第2部分提供了一个更实际的示例应用程序,展示了脚本API的更多功能。 该应用程序使用脚本API来创建动态规则引擎,其中规则被编码为用Groovy,JavaScript和Ruby编写的外部脚本。 该规则决定了房屋贷款的申请人是否符合特定抵押产品的条件。 使用Java脚本API将规则外部化,可以更改规则并在运行时添加新的抵押产品。

Java脚本API

该脚本包已于2006年12月添加到Java语言中,以提供一种将脚本语言集成到Java应用程序中的统一方法。 对于语言开发人员来说,该软件包提供了一种编写必要的粘合代码的方法,以使它们的语言可以从Java应用程序中动态调用。 对于Java开发人员,脚本包提供了一小组类和接口,这些类和接口允许使用通用API调用以多种语言编写的脚本。 因此,脚本包类似于Java数据库连接(JDBC)包,因为可以使用一致的接口将不同的语言(如不同的数据库)集成到Java平台中。

以前,涉及到Java语言来动态调用脚本语言,这涉及使用每种语言的发行版提供的独特类或使用Apache的Jakarta Bean脚本框架(BSF)。 BSF统一了单个API背后的几种脚本语言(请参阅参考资料 )。 使用主要基于BSF的Java SE 6脚本API,可以将二十多种脚本语言(包括AppleScript,Groovy,JavaScript,Jelly,PHP,Python,Ruby和Velocity)集成到Java代码中。

脚本API提供了Java应用程序和外部脚本之间的双向可见性。 您的Java代码不仅可以调用外部脚本,还可以使这些脚本访问选定的Java对象。 例如,外部Ruby脚本可以调用Java对象上的方法并访问其属性,从而允许这些脚本向正在运行的应用程序添加开发时无法预期的行为。

调用外部脚本可用于运行时应用程序增强,配置,监视或其他运行时操作,例如在不停止应用程序的情况下更改业务规则。 脚本包的可能用途包括:

  • 用比Java语言更简单的语言编写业务规则,而无需借助功能完善的规则引擎。
  • 创建一个插件架构,以使用户可以即时自定义应用程序。
  • 将现有脚本集成到Java应用程序中,例如处理或转换文本文件的脚本。
  • 使用成熟的编程语言(而不是属性文件)从外部配置应用程序的运行时行为。
  • 将特定领域的语言添加到Java应用程序。
  • 在对Java应用程序进行原型设计时使用脚本语言。
  • 用脚本语言编写应用程序测试代码。

您好,脚本世界

您可以将HelloScriptingWorld类与本文的所有代码一起下载 (请参阅下载 ),该类演示了Java脚本包的主要功能。 它使用JavaScript的硬编码片段作为示例脚本语言。 清单1中所示,该类的main()方法创建一个JavaScript脚本引擎,然后调用五个方法(如下清单所示),这些方法突出显示脚本包的功能:

清单1. HelloScriptingWorld主要方法
public static void main(String[] args) throws ScriptException, NoSuchMethodException {

ScriptEngineManager scriptEngineMgr = new ScriptEngineManager();
ScriptEngine jsEngine = scriptEngineMgr.getEngineByName("JavaScript");

    if (jsEngine == null) {
        System.err.println("No script engine found for JavaScript");
        System.exit(1);
    }

    System.out.println("Calling invokeHelloScript...");
    invokeHelloScript(jsEngine);

    System.out.println("\nCalling defineScriptFunction...");
    defineScriptFunction(jsEngine);

    System.out.println("\nCalling invokeScriptFunctionFromEngine...");
    invokeScriptFunctionFromEngine(jsEngine);

    System.out.println("\nCalling invokeScriptFunctionFromJava...");
    invokeScriptFunctionFromJava(jsEngine);

    System.out.println("\nCalling invokeJavaFromScriptFunction...");
    invokeJavaFromScriptFunction(jsEngine);
}

main()方法的主要功能是获取javax.script.ScriptEngine的实例( 清单1中的前两个语句)。 脚本引擎以特定语言加载和执行脚本。 它是Java脚本包中最常用和最重要的类。 您可以从javax.script.ScriptEngineManager (第一条语句)获得脚本引擎。 除非使用许多脚本语言,否则典型程序仅需要获取脚本引擎的一个实例。

ScriptEngineManager类

ScriptEngineManager可能是您将定期使用的脚本包中唯一的具体类。 其余大多数是接口。 并且它可能是脚本包中唯一要直接实例化的类(或通过诸如Spring Framework这样的依赖注入机制间接实例化) ScriptEngineManager可以通过以下三种方式之一返回脚本引擎:

  • 按引擎或语言名称,如清单1所示,请求一个JavaScript引擎。
  • 使用该语言的脚本常用的文件扩展名,例如Ruby脚本的.rb。
  • 通过MIME类型,脚本引擎已声明知道如何处理。

ScriptEngineManager可以间接查找和创建脚本引擎。 也就是说,实例化脚本引擎管理器时,他们使用Java 6中添加的服务发现机制来查找类路径中所有已注册的javax.script.ScriptEngineFactory实现。 这些工厂类与Java脚本API实现一起打包。 您可能永远不需要直接处理这些工厂类。

一旦ScriptEngineManager找到了所有脚本引擎工厂类,它就会查询每个脚本引擎工厂类,以查明是否可以创建所请求类型的脚本引擎-在清单1的情况下为JavaScript引擎。 如果工厂表示可以为所需的语言创建脚本引擎,则经理要求工厂创建一个脚本引擎,然后将其返回给调用方。 如果没有找到所请求语言的工厂,那么管理器将返回null清单1中的代码通过检查null返回值来防止这种情况的发生。

ScriptEngine介面

如前所述,您的代码使用ScriptEngine实例执行脚本。 脚本引擎充当脚本代码与最终执行代码的基础语言解释器或编译器之间的中介。 这样,您无需知道每个解释器用来执行代码的类。 例如,用于JRuby的脚本引擎可能会将您的代码传递给JRuby的org.jruby.Ruby类的实例,以首先将脚本编译为中间形式,然后再次调用它以评估脚本并处理返回值。 脚本引擎实现隐藏了细节,包括解释器如何与Java代码共享类定义,应用程序对象以及输入/输出流。

图1显示了应用程序,Java脚本API, ScriptEngine实现和脚本语言解释器之间的一般关系。 您可以看到您的应用程序仅依赖于脚本API,该API提供ScriptEngineManager类和ScriptEngine接口。 ScriptEngine实现组件处理使用特定脚本语言解释器的细节。

图1:脚本API组件关系
脚本API组件图

您可能想知道在哪里可以获取脚本引擎实现和语言解释器所需的JAR文件。 脚本引擎实现的最佳选择首先是由java.net托管的开源Scripting项目(请参阅参考资料 )。 在这里,您会找到多种语言的脚本引擎实现,以及指向其他位置托管的脚本引擎实现的链接。 脚本项目还提供了下载解释器的链接,以支持其支持的脚本语言。

清单1中main()方法将ScriptEngine传递给每个方法,以用于评估该方法JavaScript代码。 清单2中显示了第一个方法invokeHelloScript()方法调用脚本引擎的eval方法来评估和执行给定JavaScript代码字符串。 ScriptEngine接口定义了六个重载的eval()方法,这些方法接受一个脚本来评估为字符串还是java.io.Reader对象,该对象通常用于从文件等外部来源读取脚本。

清单2. invokeHelloScript方法
private static void invokeHelloScript(ScriptEngine jsEngine) throws ScriptException {
    jsEngine.eval("println('Hello from JavaScript')");
}

invokeHelloScript()方法中的Hello from JavaScriptHello from JavaScript信号输出到标准输出流,在本例中为控制台窗口。 ( 清单6包含了运行HelloScriptingWorldApplication的完整输出。)

注意,该类中的this和其他方法声明它们抛出javax.script.ScriptException 。 此已检查的异常-由脚本包定义的唯一异常-表明引擎未能解析或执行给定的代码。 所有脚本引擎的eval()方法都声明它们抛出ScriptException ,因此您的代码需要适当地处理它。

清单3显示了两个相关方法: defineScriptFunction()invokeScriptFunctionFromEngine()defineScriptFunction()方法还使用JavaScript的硬编码代码段调用脚本引擎的eval()方法。 但是请注意,此方法只定义了一个JavaScript函数sayHello() 。 没有代码正在执行。 sayHello()函数采用一个参数,该参数在下面的println()语句中输出到控制台。 脚本引擎JavaScript解释器将此函数添加到其全局环境中,使其可在随后的eval调用中使用(这并不奇怪)在invokeScriptFunctionFromEngine()方法中。

清单3. defineScriptFunction和invokeScriptFunctionFromEngine方法
private static void defineScriptFunction(ScriptEngine engine) throws ScriptException {
    // Define a function in the script engine
    engine.eval(
        "function sayHello(name) {" +
        "    println('Hello, ' + name)" +
        "}"
    );
}

private static void invokeScriptFunctionFromEngine(ScriptEngine engine)
  throws ScriptException
{
    engine.eval("sayHello('World!')");
}

这对方法演示了脚本引擎可以维护应用程序组件的状态,并使该状态在后续调用引擎的eval()方法期间可用。 invokeScriptFunctionFromEngine()方法通过调用在先前对eval()调用中定义的sayHello() JavaScript函数来利用保持状态的优势。

许多脚本引擎在调用eval()之间维护全局变量和函数的状态。 但是,请务必注意,Java脚本API不需要脚本引擎来提供此功能。 本文中使用JavaScript,Groovy和JRuby脚本引擎确实在eval()调用之间保持状态。

清单4是前面示例的变形。 invokeScriptFunctionFromJava()方法的不同之处在于,它无需使用ScriptEngineeval()方法或JavaScript代码即可调用sayHello() JavaScript函数。 相反,它使用Java脚本API的javax.script.Invocable接口来调用由脚本引擎维护的函数。 invokeScriptFunctionFromJava()方法将脚本引擎对象强制转换为Invocable接口,然后在该接口上调用invokeFunction()方法,以使用给定的参数调用sayHello() JavaScript函数。 如果被调用的函数返回一个值,则invokeFunction()方法将其包装为Java Object类型返回。

清单4. invokeScriptFunctionFromJava方法
private static void invokeScriptFunctionFromJava(ScriptEngine engine)
  throws ScriptException, NoSuchMethodException
{
Invocable invocableEngine = (Invocable) engine;
    invocableEngine.invokeFunction("sayHello", "from Java");
}

请注意, 清单4不包含JavaScript。 Invocable接口允许Java代码在不知道其实现语言的情况下调用脚本函数。 如果脚本引擎找不到具有给定名称或参数类型的函数,则invokeFunction()方法将引发java.lang.NoSuchMethodException

Java脚本API不需要脚本引擎来实现Invocable接口。 实际上, 清单4中的代码应该使用instanceof运算符来确保脚本引擎在进行Invocable之前实现Invocable接口。

从脚本代码调用Java方法

清单3清单4中的示例显示Java代码如何调用脚本语言中定义的函数或方法。 您可能想知道用脚本语言编写的代码是否可以依次调用Java对象上的方法。 它可以。 清单5中的invokeJavaFromScriptFunction()方法显示了如何使脚本引擎访问Java对象,以及脚本代码如何调用该Java对象上的方法。 具体来说, invokeJavaFromScriptFunction()方法使用脚本引擎的put()方法向引擎提供HelloScriptingWorld类本身的实例。 引擎使用对put()的调用中提供的名称访问Java对象后,对eval()方法的调用中的脚本代码就会使用它。

清单5. invokeJavaFromScriptFunction和getHelloReply方法
private static void invokeJavaFromScriptFunction(ScriptEngine engine)
  throws ScriptException
{
    engine.put("helloScriptingWorld", new HelloScriptingWorld());
    engine.eval(
        "println('Invoking getHelloReply method from JavaScript...');" +
        "var msg = helloScriptingWorld.getHelloReply(vJavaScript');" +
        "println('Java returned: ' + msg)"
    );
}

/** Method invoked from the above script to return a string. */
public String getHelloReply(String name) {
    return "Java method getHelloReply says, 'Hello, " + name + "'";
}

清单5调用eval()方法中包含JavaScript代码通过使用脚本引擎put()方法调用中提供的变量名helloScriptingWorld来访问HelloScriptingWorld Java对象。 JavaScript代码的第二行调用getHelloReply()公共Java方法,如清单5所示。 getHelloReply()方法返回Java method getHelloReply says, 'Hello, <parameter>'字符串。 eval()方法中JavaScript代码将Java返回值分配给msg变量,然后将其输出到控制台。

ScriptEngine.put及其关联的get()方法是在Java代码和脚本引擎中运行的脚本之间共享对象和数据的主要方式。 (有关此主题的扩展讨论,请参见本文后面的脚本执行范围 。)当您调用引擎的put()方法时,脚本引擎会将第二个参数(任何Java对象)与给定的字符串键相关联。 大多数脚本引擎使这些Java对象可以使用给定变量名的脚本进行访问。 脚本引擎可以随意使用传递给put()方法的名称。 例如,JRuby脚本引擎使helloScriptingWorld可用于全局$helloScriptingWorld变量下的Ruby代码,以适合全局变量的Ruby语法。

脚本引擎的get()方法检索脚本环境中可用的值。 通常,可以通过get()方法从Java代码访问脚本环境中的每个全局变量和函数。 但是,脚本只能访问那些使用put()与脚本引擎显式共享的Java对象。

外部脚本访问和操作正在运行的应用程序中的Java对象的能力是扩展Java程序功能的强大技术。 ( 第2部分中的示例利用了该技术。)

运行HelloScriptingWorld应用程序

您可以通过下载并构建源代码来运行HelloScriptingWorld应用程序。 .zip文件包含一个Ant脚本和一个Maven构建文件,以帮助编译和运行示例应用程序。 跟着这些步骤:

  1. 下载 .zip文件。
  2. 创建一个新目录,例如java-scripting,然后将您在步骤1中下载的文件解压缩到该目录中。
  3. 打开命令行外壳,然后转到该目录。
  4. 运行ant run-hello

您应该看到来自Ant的控制台输出,与清单6中的输出类似。请注意, 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文件放在类路径中。 这些文件提供了Java SE 6附带的脚本API,JavaScript脚本引擎接口和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概念起初可能会造成混淆,因为它们的含义重叠。 本文的源代码下载文件在src / test / java目录中包含一个名为ScriptApiRhinoTest的JUnit测试文件,以帮助通过Java代码解释这些概念。

下一步是什么?

既然您已经对Java脚本API有了基本的了解,那么本文的第2部分将使用更实际的示例应用程序来完善和扩展该知识。 该应用程序使用结合了Groovy,Ruby和JavaScript编写的外部脚本文件来定义可以在运行时更改的业务逻辑。 就像您将看到的那样,用脚本语言定义业务规则可以使规则更易于编写,并且可能使非程序员(例如业务分析师或规则编写者)更易于阅读。


翻译自: https://www.ibm.com/developerworks/java/library/j-javascripting1/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值