兼容java的脚步语言_Java SE 6 新特性: 对脚本语言的支持

脚本引擎
脚本引擎就是指脚本的运行环境,它能能够把运行其上的解释性语言转换为更底层的汇编语言,没有脚本引擎,脚本就无法被运行 。
Java SE 6 引入了对 Java(JSR)223 的支持,JSR 223 旨在定义一个统一的规范,使得 Java 应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在 Java 平台上调用各种脚本语言的目的 。javax.
包定义了这些接口,即 Java 脚本编程 API 。Java 脚本 API 的目标与项目 Bean
(BSF)类似 , 通过它 Java 应用程序就能通过虚拟机调用各种脚本 , 同时,脚本语言也能访问应用程序中的 Java
对象和方法 。Java 脚本 API 是连通 Java 平台和脚本语言的桥梁 。首先,通过它为数众多的现有 Java
库就能被各种脚本语言所利用,节省了开发成本缩短了开发周期;其次 , 可以把一些复杂异变的业务逻辑交给脚本语言处理,这又大大提高了开发效率 。
在 javax. 包中定义的实现类并不多 , 主要是一些接口和对应的抽象类 , 图 1 显示了其中包含的各个接口和类 。
图 1. javax. 包概况

个包的具体实现类少的根本原因是这个包只是定义了一个编程接口的框架规范,至于对如何解析运行具体的脚本语言,还需要由第三方提供实现 。虽然这些脚本引擎
的实现各不相同,但是对于 Java 脚本 API 的使用者来说 , 这些具体的实现被很好的隔离隐藏了 。Java 脚本 API
为开发者提供了如下功能:
获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果,这是最核心的接口 。
发现脚本引擎 , 查询脚本引擎信息 。
通过脚本引擎的运行上下文在脚本和 Java 平台间交换数据 。
通过 Java 应用程序调用脚本函数 。
在详细介绍这四个功能之前,我们先通过一个简单的例子来展示如何通过 Java 语言来运行脚本程序,这里仍然以经典的“Hello World”开始 。
清单 1. Hello World
..*;d{([]args)n{
=er();
=.("");
.eval("print('')");
这个例子非常直观,只要通过和这两个类就可以完成最简单的调用 。首先 ,  实例创建一个实例 , 然后返回的实例解析脚本,输出运行结果 。运行这段程序,终端上会输出“Hello World“ 。在执行 eval 函数的过程中可能会有异常抛出,引发这个异常被抛出的原因一般是由脚本输入语法有误造成的 。在对整个 API 有了大致的概念之后,我们就可以开始介绍各个具体的功能了 。
Java
脚本 API 通过脚本引擎来运行脚本,整个包的目的就在于统一 Java 平台与各种脚本引擎的交互方式 , 制定一个标准,Java
应用程序依照这种标准就能自由的调用各种脚本引擎,而脚本引擎按照这种标准实现,就能被 Java
平台支持 。每一个脚本引擎就是一个脚本解释器,负责运行脚本,获取运行结果 。接口是脚本引擎在 Java 平台上的抽象 , Java 应用程序通过这个接口调用脚本引擎运行脚本程序,并将运行结果返回给虚拟机 。
接口提供了许多 eval 函数的变体用来运行脚本,这个函数的功能就是获取脚本输入 , 运行脚本 , 最后返回输出 。清单 1 的例子中直接通过字符串作为 eval 函数的参数读入脚本程序 。除此之外,还提供了以一个 java.io. 作为输入参数的 eval 函数 。脚本程序实质上是一些可以用脚本引擎执行的字节流,通过一个对象,eval 函数就能从不同的数据源中读取字节流来运行 , 这个数据源可以来自内存、文件,甚至直接来自网络 。这样 Java 应用程序就能直接利用项目原有的脚本资源,无需以 Java 语言对其进行重写,达到脚本程序与 Java 平台无缝集成的目的 。清单 2 即展示了如何从一个文件中读取脚本程序并运行 , 其中如何通过获取实例的细节会在后面详细介绍 。
清单 2. Run
{([]args){
=args[0];
=args[1];
er=((file));
=er();
=.();
.eval();
清单 2 代码,从命令行分别获取脚本名称和脚本文件名,程序通过脚本名称创建对应的脚本引擎实例 , 通过脚本名称指定的脚本文件名读入脚本程序运行 。运行下面这个命令,就能在 Java 平台上运行所有的脚本 。
.js
通过这种方式,Java 应用程序可以把一些复杂易变的逻辑过程,用更加灵活的弱类型的脚本语言来实现,然后通过 javax. 包提供的 API 获取运行结果,当脚本改变时,只需替换对应的脚本文件,而无需重新编译构建项目,好处是显而易见的,即节省了开发时间又提高了开发效率 。
接口分别针对输入和输入提供了三个不同形态的 eval 函数,用于运行脚本:
表 1.的 eval 函数
函数描述
eval( )
从一个读取脚本程序并运行
eval( ,n)
以 n 作为脚本级别的绑定,从一个读取脚本程序并运行
eval( ,)
在指定的上下文环境下 , 从一个读取脚本程序并运行
eval( )
运行字符串表示的脚本
eval( ,n)
以 n 作为脚本级别的绑定,运行字符串表示的脚本
eval( ,)
在指定的上下文环境下 , 运行字符串表示的脚本
Java 脚本 API 还为接口提供了一个抽象类 ——  , 这个类提供了其中四个 eval 函数的默认实现,它们分别通过调用 eval(,) 或 eval(, ) 来实现 。这样脚本引擎提供者 , 只需继承这个抽象类并提供这两个函数实现即可 。有一个保护域用于保存默认上下文的引用,类被作为的默认上下文 。关于上下文环境,将在后面进行详细介绍 。
在前面的两个例子中,实例都是通过调用实例的方法返回的,而不是常见的直接通过 new 操作新建一个实例 。JSR 223 中引入类的意义就在于,将的寻找和创建任务委托给实例处理,达到对 API 使用者隐藏这个过程的目的 , 使 Java 应用程序在无需重新编译的情况下,支持脚本引擎的动态替换 。通过类和接口即可完成脚本引擎的发现和创建:
类:自动寻找接口的实现类
接口:创建合适的脚本引擎实例
服务()是指那些成为事实上标准的接口,服务提供者( )则提供了这个接口的具体实现 。不同的提供者会遵循同样的接口提供实现,客户可以自由选择不同的实现 。可以从 Sun 提供的文档 Jar 文件规约 中获取有关更详细的信息 。
类本身并不知道如何创建一个具体的脚本引擎实例,它会依照 Jar 规约中定义的服务发现机制 , 查找并创建一个合适的实例,并通过这个工厂类来创建返回实际的脚本引擎 。首先, 实例会在当前中搜索所有可见的 Jar 包;然后 , 它会查看每个 Jar 包中的 META -INF// 目录下的是否包含 javax.. 文件,脚本引擎的开发者会提供在 Jar 包中包含一个接口的实现类,这个文件内容即是这个实现类的完整名字; 会根据这个类名,创建一个接口的实例;最后 , 通过这个工厂类来实例化需要的脚本引擎,返回给用户 。举例来说,第三方的引擎提供者可能升级更新了新版的脚本引擎实现,通过来管理脚本引擎 , 无需修改一行 Java 代码就能替换更新脚本引擎 。用户只需在中加入新的脚本引擎实现(Jar 包的形式),就能通过机制来自动查找到新版本实现,创建并返回对应的脚本引擎实例供调用 。图 2 所示时序图描述了其中的步骤:
图 2. 脚本引擎发现机制时序图
接口的实现类被用来描述和实例化接口 , 每一个实现接口的类会有一个对应的工厂类来描述其元数据(meta

兼容java的脚步语言_Java SE 6 新特性: 对脚本语言的支持

文章插图
data),接口定义了许多函数供查询这些元数据,会根据这些元数据查找需要的脚本引擎,表 2列出了可供使用的函数:
表 2.提供的查询函数
函数描述
()
返回脚本引擎的全称
()
返回脚本引擎的版本信息
()
返回脚本引擎所支持的脚本语言的名称
()
返回脚本引擎所支持的脚本语言的版本信息
List ()
返回一个脚本文件扩展名组成的 List,当前脚本引擎支持解析这些扩展名对应的脚本文件
List ()
返回一个与当前引擎关联的所有组成的 List
List ()
返回一个当前引擎所有名称的 List,可以根据这些名字确定对应的脚本引擎
通过 () 函数 ,  会返回一个包含当前环境中被发现的所有实现接口的具体类,通过这些工厂类中保存的脚本引擎信息检索需要的脚本引擎 。第三方提供的脚本引擎实现的 Jar 包中除了包含接口的实现类之外,还需要提供接口的实现类,以及一个 javax.. 文件用于指明这个工厂类 。这样,Java 平台就能通过寻找到这个工厂类,并通过这个工厂类为用户提供一个脚本引擎实例 。Java SE 6 默认提供了脚本引擎的实现,如果需要支持其他脚本引擎,需要将它们对应的 Jar 包包含在中,比如对于前面 清单 2 中的代码 , 只需在运行程序前将的脚本引擎添加到中,然后运行:
javarun.
无需修改一行 Java 代码就能以脚本引擎来运行脚本 。在 这里
为 Java SE 6 提供了许多著名脚本语言的脚本引擎对 JSR 223 的支持,这些 Jar 必须和脚本引擎配合使用,使得这些脚本语言能被
Java 平台支持 。到目前为止 , 它提供了至少 25 种脚本语言的支持,其中包括了 、Ruby、
等当前非常流行的脚本语言 。这里需要再次强调的是,负责创建实例的实现类对于用户来说是不可见的,实现负责与其交互,通过它创建脚本引擎 。

果仅仅是通过脚本引擎运行脚本的话,还无法体现出 Java 脚本 API 的优点,在 JSR 223
中,还为所有的脚本引擎定义了一个简洁的执行环境 。我们都知道,在 Linux 操作系统中可以维护许多环境变量比如 、path
等,不同的 shell 在运行时可以直接使用这些环境变量,它们构成了 shell 脚本的执行环境 。在 javax. 支持的每个脚本引擎也有各自对应的执行的环境 , 脚本引擎可以共享同样的环境 , 也可以有各自不同的上下文 。通过脚本运行时的上下文 , 脚本程序就能自由的和 Java 平台交互 , 并充分利用已有的众多 Java API,真正的站在“巨人”的肩膀上 。javax.. 接口和 javax.. 接口定义了脚本引擎的上下文 。
接口:
继承自 Map,定义了对这些“键-值”对的查询、添加、删除等 Map 典型操作 。接口实际上是一个存放数据的容器,它的实现类会维护许多“键-值”对,它们都通过字符串表示 。Java 应用程序和脚本程序通过这些“键-值”对交换数据 。只要脚本引擎支持,用户还能直接在中放置 Java 对象,脚本引擎通过不仅可以存取对象的属性,还能调用 Java 对象的方法,这种双向自由的沟通使得二者真正的结合在了一起 。
接口:
将和联系在了一起,每一个都有一个对应的,前面提到过通过创建脚本引擎除了达到隐藏实现的目的外,还负责为脚本引擎设置合适的上下文 。通过实例就能从其内部的中获得需要的属性值 。接口默认包含了两个级别的实例的引用,分别是全局级别和引擎级别,可以通过和这两个类常量来界定区分这两个实例,其中 从创建它的获得 。顾名思义 , 全局级别指的是里的属性都是“全局变量”,只要是同一个返回的脚本引擎都可以共享这些属性;对应的,引擎级别的里的属性则是“局部变量”,它们只对同一个引擎实例可见,从而能为不同的引擎设置独特的环境,通过同一个脚本引擎运行的脚本运行时能共享这些属性 。
接口定义了下面这些函数来存取数据:
表 3.存取属性函数
函数描述
( name, int scope)
从指定的范围里删除一个属性
void ( name,value, int scope)
在指定的范围里设置一个属性的值
( name)
从上下文的所有范围内获取优先级最高的属性的值
( name, int scope)
从指定的范围里获取属性值
拥有一个全局性的实例 , 在通过实例创建后,它把自己的这个传递给所有它创建的实例,作为。同时,每一个实例都对应一个实例,这个除了从那获得的  , 自己也维护一个的实例,所有通过这个脚本引擎运行的脚本,都能存取其中的属性 。除了可以设置属性,改变内部的,Java 脚本 API 为和也提供了类似的设置属性和的 API 。
图 3.在 Java 脚本 API 中的分布
从 图 3 中可以看到,共有三个级别的地方可以存取属性 , 分别是中的 ,实例对应的中含有的 ,以及调用 eval 函数时传入的。离函数调用越近 , 其作用域越小,优先级越高,相当于编程语言中的变量的可见域,即( name) 中提到的优先级 。从 清单 3 这个例子中可以看出各个属性的存取优先级:
清单 3. 上下文属性的作用域
..*;{([]args){
="()";
=er();
=.("");//.put("","");
.eval();//.put("","e");
.eval();//ext=xt();
.("","",
.);
.eval(,);
脚本 () 在这个程序中被重复调用了三次,由于三次调用的环境不一样,导致输出也不一样,变量每一次都被优先级更高的也就是距离函数调用越近的值覆盖 。从这个例子同时也演示了如何使用和这两个接口,在例子脚本中并没有定义这个变量,但是脚本通过 Java 脚本 API 能方便的存取 Java 应用程序中的对象 , 输出相应的值 。运行这个程序后,能看到输出为:
图 4. 程序的输出
除了能在 Java 平台与脚本程序之间的提供共享属性之外, 还允许用户重定向引擎执行时的输入输出流:
表 4.输入输出重定向
函数描述
void ( )
重定向错误输出,默认是标准错误输出
void ( )
重定向输入,默认是标准输入
void ( )
重定向输出,默认是标准输出
()
获取当前错误输出字节流
()
获取当前输入流
()
获取当前输出流
清单 4 展示了如何通过将其对应的标准输出重定向到一个中,用户可以通过与这个连通的读取实际的输出,使 Java 应用程序能获取脚本运行输出,满足更加多样的应用需求 。
清单 4. 重定向脚本输出
.io.*;..*;ry{([]args){
=er();
=.("");
=();
=(pr);
=(pw);
.().();
="('')";
.eval();
=(pr);
.out.(br.());
Java 脚本 API 分别为这两个接口提供了一个简单的实现供用户使用 。通过组合模式实现 Map 接口,它提供了两个构造函数 。无参构造函数在内部构造一个实例来实现 Map 接口要求的功能;同时,也提供了一个以 Map 接口作为参数的构造函数,允许任何实现 Map 接口的类作为其组合的实例,以满足不同的要求 。提供了简单实现 。默认情况下,它使用了标准输入、标准输出和标准错误输出,同时维护一个作为其引擎级别的,它的默认全局级别为空 。
接口:允许 Java 平台调用脚本程序中的函数或方法 。
接口:允许 Java 平台编译脚本程序,供多次调用 。
有时候,用户可能并不需要运行已有的整个脚本程序,而仅仅需要调用其中的一个过程,或者其中某个对象的方法,这个时候接口就能发挥作用 。它提供了两个函数和,分别允许 Java 应用程序直接调用脚本中的一个全局性的过程以及对象中的方法,调用后者时,除了指定函数名字和参数外,还需要传入要调用的对象引用,当然这需要脚本引擎的支持 。不仅如此,接口还允许 Java 应用程序从这些函数中直接返回一个接口 , 通过这个接口实例来调用脚本中的函数或方法 , 从而我们可以从脚本中动态的生成 Java 应用中需要的接口对象 。清单 5 演示了如何使用一个接口:
清单 5. 调用脚本中的函数
..*;eTest{([]args)n,
n{
="(){();}";
=er();
=.("");
.eval();if(cable){
=();
.("","hi");//{
.("");
}catch(ne){//}
在调用函数前 , 可以先通过操作判断脚本引擎是否支持编译操作,防止类型转换时抛出运行时异常,需要特别注意的时 , 如果调用了脚本程序中不存在的函数时,运行时会抛出一个 n 的异常,实际开发中应该注意处理这种特殊情况 。

般来说,脚本语言都是解释型的,这也是脚本语言区别与编译语言的一个特点,解释性意味着脚本随时可以被运行 , 开发者可以边开发边查看接口,从而省去了编译
这个环节,提供了开发效率 。但是这也是一把双刃剑,当脚本规模变大,重复解释一段稳定的代码又会带来运行时的开销 。有些脚本引擎支持将脚本运行编译成某种
中间形式,这取决与脚本语言的性质以及脚本引擎的实现 , 可以是一些操作码,甚至是 Java
字节码文件 。实现了这个接口的脚本引擎能把输入的脚本预编译并缓存,从而提高多次运行相同脚本的效率 。
Java 脚本 API 还为这个中间形式提供了一个专门的类,每次调用接口的编译函数都会返回一个实例 。类被用来保存编译的结果,从而能重复调用脚本而没有重复解释的开销 , 实际效率提高的多少取决于中间形式的彻底程度,其中间形式越接近低级语言,提高的效率就越高 。每一个实例对应于一个脚本引擎实例,一个脚本引擎实例可以含有多个 (这很容易理解) , 调用的 eval 函数会传递给这个关联的的 eval 函数 。关于类需要注意的是,它运行时对与之对应的状态的改变可能会传递给下一次调用,造成运行结果的不一致 。清单 6 演示了如何使用接口来调用脚本:
清单 6. 编译脚本
..*;eTest{([]args)n{
="();='!'";
=er();
=.("");
.put("","!");if(){
=();
=pile();
.eval();
.eval();
与类似,也应该先通过操作判断脚本引擎是否支持编译操作,防止预料外的异常抛出 。并且我们可以发现同一段编译过的脚本,在第二次运行时变量的内容被上一次的运行改变了,导致输出不一致:
图 5. 程序的输出
Java
SE 6 还为运行脚本添加了一个专门的工具 ——。
支持两种运行方式:一种是交互式,即边读取边解析运行,这种方式使得用户可以方便调试脚本程序,马上获取预期结果;还有一种就是批处理式 , 即读取并运行整
个脚本文件 。用户可以把它想象成一个万能脚本解释器,即它可以运行任意脚本程序,而且它还是跨平台的,当然所有这一切都有一个前提,那就是必须告诉它相应
的脚本引擎的位置 。默认即支持的脚本是 ,这意味着用户可以无需任何设置,通过在任何支持 Java
的平台上运行任何脚本;如果想运行其他脚本,可以通过 -l 指定以何种脚本引擎运行脚本 。不过这个工具仍是实验性质的,不一定会包含在 Java 的后续版本中,无论如何,它仍是一个非常有用的工具 。
在 Java 平台上使用脚本语言编程非常方便,因为 Java 脚本 API 相对其他包要小很多 。通过 javax.
包提供的接口和类我们可以很方便为我们的 Java 应用程序添加对脚本语言的支持 。开发者只要遵照 Java 脚本 API
开发应用程序,开发中就无需关注具体的脚本语言细节,应用程序就可以动态支持任何符合 JSR 223 标准的脚本语言 , 不仅如此,只要按照 JSR
223 标准开发,用户甚至还能为 Java 平台提供一个自定义脚本语言的解释器 。在 Java
平台上运行自己的脚本语言,这对于众多开发者来说都是非常有诱惑力的 。
尽管千里冰封
依然拥有晴空
【兼容java的脚步语言_Java SE 6 新特性: 对脚本语言的支持】你我共同品味JAVA的浓香.