热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java脚本api_Java脚本API简介

Java开发人员知道Java语言并不总是每种任务的最佳语言。今年的JRuby和Groovy的1.0版本发行了对向Java应用程序添加动态语言的兴趣。借助Groovy,

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 engineengine.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, '字符串。 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 JavascriptCalling defineScriptFunction...Calling invokeScriptFunctionFromEngine...
Hello, World!Calling invokeScriptFunctionFromJava...
Hello, from JavaCalling 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




推荐阅读
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 2018深入java目标计划及学习内容
    本文介绍了作者在2018年的深入java目标计划,包括学习计划和工作中要用到的内容。作者计划学习的内容包括kafka、zookeeper、hbase、hdoop、spark、elasticsearch、solr、spring cloud、mysql、mybatis等。其中,作者对jvm的学习有一定了解,并计划通读《jvm》一书。此外,作者还提到了《HotSpot实战》和《高性能MySQL》等书籍。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • Servlet多用户登录时HttpSession会话信息覆盖问题的解决方案
    本文讨论了在Servlet多用户登录时可能出现的HttpSession会话信息覆盖问题,并提供了解决方案。通过分析JSESSIONID的作用机制和编码方式,我们可以得出每个HttpSession对象都是通过客户端发送的唯一JSESSIONID来识别的,因此无需担心会话信息被覆盖的问题。需要注意的是,本文讨论的是多个客户端级别上的多用户登录,而非同一个浏览器级别上的多用户登录。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 开发笔记:Python之路第一篇:初识Python
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Python之路第一篇:初识Python相关的知识,希望对你有一定的参考价值。Python简介& ... [详细]
  • Android系统启动过程分析一、Android平台架构首先贴一张Android系统架构图方便理解整个Android架构,这可以让我们从整体上对整个启动流程有个大概认知。可以看出整 ... [详细]
  • 与.Net大师Jeffrey Richter面对面交流——TUP对话大师系列活动回顾(多图配详细文字)...
    与.Net大师JeffreyRichter面对面交流——TUP对话大师系列活动回顾(多图配文字)上周末很有幸参加了CSDN举行的TUP活动, ... [详细]
  • 只使用’if-else’语句的’else’部分是否可以接受?有时,我觉得检查所有条件是否都是真的更容易,但是只处理“其他”情况。我想 ... [详细]
  • rust编程这篇文章是关于我通过解决Twitch上尚未解决的所有CtCI问题来学习Rust的经验。英国科学博物馆集团AdaLovelace的肖像Rust徽标,由Moz ... [详细]
author-avatar
轻裾随风远
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有