SVN8.COM - SVN中文技术网

投递文章 投稿指南 SVN中文技术网公告:技术交流诚聘优秀版主最新公告
搜索: 您的位置主页>JAVA技术>J2SE>Java SE 6 新特性之 对脚本语言的支持

Java SE 6 新特性之 对脚本语言的支持

SVN技术网 www.svn8.com 2008-10-07 00:09:45   来源:   作者:  评论:0 点击:
脚本引擎就是指脚本的运行环境,它能能够把运行其上的解释性语言转换为更底层的汇编语言,没有脚本引擎,脚本就无法被运行。
Java SE 6 引入了对 Java Specification Request(JSR)223 的支持,JSR 223旨在定义一个统一的规范,使得 Java 应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在 Java 平台上调用各种脚本语言的目的。javax.script包定义了这些接口,即 Java 脚本编程 API。Java 脚本 API 的目标与 Apache 项目 Bean Script Framework(BSF)类似,通过它 Java 应用程序就能通过虚拟机调用各种脚本,同时,脚本语言也能访问应用程序中的 Java 对象和方法。Java 脚本 API 是连通 Java 平台和脚本语言的桥梁。首先,通过它为数众多的现有 Java 库就能被各种脚本语言所利用,节省了开发成本缩短了开发周期;其次,可以把一些复杂异变的业务逻辑交给脚本语言处理,这又大大提高了开发效率。

  在 javax.script包中定义的实现类并不多,主要是一些接口和对应的抽象类,图 1显示了其中包含的各个接口和类。

  图 1. javax.script 包概况

  这个包的具体实现类少的根本原因是这个包只是定义了一个编程接口的框架规范,至于对如何解析运行具体的脚本语言,还需要由第三方提供实现。虽然这些脚本引擎的实现各不相同,但是对于
Java 脚本 API 的使用者来说,这些具体的实现被很好的隔离隐藏了。Java 脚本 API 为开发者提供了如下功能:

  获取脚本程序输入,通过脚本引擎运行脚本并返回运行结果,这是最核心的接口。

  发现脚本引擎,查询脚本引擎信息。

  通过脚本引擎的运行上下文在脚本和
Java 平台间交换数据。

  通过
Java 应用程序调用脚本函数。

  在详细介绍这四个功能之前,我们先通过一个简单的例子来展示如何通过
Java 语言来运行脚本程序,这里仍然以经典的“Hello World”开始。

  清单 1. Hello World

  



import javax.script.*;

public class HelloWorld {

public static void main(String[] args) throws ScriptException {

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("
JavaScript");

engine.eval("print ('Hello World')");

}

}



  这个例子非常直观,只要通过 ScriptEngineManager和 ScriptEngine这两个类就可以完成最简单的调用。首先,ScriptEngineManager实例创建一个 ScriptEngine实例,然后返回的 ScriptEngine实例解析
JavaScript 脚本,输出运行结果。运行这段程序,终端上会输出“Hello World“。在执行 eval函数的过程中可能会有 ScriptEngine异常抛出,引发这个异常被抛出的原因一般是由脚本输入语法有误造成的。在对整个 API 有了大致的概念之后,我们就可以开始介绍各个具体的功能了。

  使用脚本引擎运行脚本

  
Java 脚本 API 通过脚本引擎来运行脚本,整个包的目的就在于统一 Java 平台与各种脚本引擎的交互方式,制定一个标准,Java 应用程序依照这种标准就能自由的调用各种脚本引擎,而脚本引擎按照这种标准实现,就能被 Java 平台支持。每一个脚本引擎就是一个脚本解释器,负责运行脚本,获取运行结果。ScriptEngine接口是脚本引擎在 Java 平台上的抽象,Java 应用程序通过这个接口调用脚本引擎运行脚本程序,并将运行结果返回给虚拟机。

  ScriptEngine接口提供了许多 eval函数的变体用来运行脚本,这个函数的功能就是获取脚本输入,运行脚本,最后返回输出。清单 1的例子中直接通过字符串作为 eval函数的参数读入脚本程序。除此之外,ScriptEngine还提供了以一个 java.io.Reader作为输入参数的 eval函数。脚本程序实质上是一些可以用脚本引擎执行的字节流,通过一个 Reader对象,eval函数就能从不同的数据源中读取字节流来运行,这个数据源可以来自内存、文件,甚至直接来自网络。这样
Java 应用程序就能直接利用项目原有的脚本资源,无需以 Java 语言对其进行重写,达到脚本程序与 Java 平台无缝集成的目的。清单 2即展示了如何从一个文件中读取脚本程序并运行,其中如何通过 ScriptEngineManager获取 ScriptEngine实例的细节会在后面详细介绍。

  清单 2. Run Script

  



public class RunScript {

public static void main(String[] args) throws Exception {

String script = args[0];

String file = args;



FileReader scriptReader = new FileReader(new File(file));

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName(script);

engine.eval(scriptReader);

}

}



  清单 2代码,从命令行分别获取脚本名称和脚本文件名,程序通过脚本名称创建对应的脚本引擎实例,通过脚本名称指定的脚本文件名读入脚本程序运行。运行下面这个命令,就能在
Java 平台上运行所有的 JavaScript 脚本。

  

java RunScript javascript run.js



  通过这种方式,
Java 应用程序可以把一些复杂易变的逻辑过程,用更加灵活的弱类型的脚本语言来实现,然后通过 javax.Script包提供的 API 获取运行结果,当脚本改变时,只需替换对应的脚本文件,而无需重新编译构建项目,好处是显而易见的,即节省了开发时间又提高了开发效率。

  EngineScript接口分别针对 String输入和 Reader输入提供了三个不同形态的 eval函数,用于运行脚本:

  表 1. ScriptEngine 的 eval 函数

  函数 描述

  Object eval(Reader reader) 从一个 Reader读取脚本程序并运行

  Object eval(Reader reader, Bindings n) 以 n作为脚本级别的绑定,从一个 Reader读取脚本程序并运行

  Object eval(Reader reader, ScriptContext context) 在 context指定的上下文环境下,从一个 Reader读取脚本程序并运行

  Object eval(String script) 运行字符串表示的脚本

  Object eval(String script, Bindings n) 以 n作为脚本级别的绑定,运行字符串表示的脚本

  Object eval(String script, ScriptContext context) 在 context指定的上下文环境下,运行字符串表示的脚本

  
Java 脚本 API 还为 ScriptEngine接口提供了一个抽象类 —— AbstractScriptEngine,这个类提供了其中四个 eval函数的默认实现,它们分别通过调用 eval(Reader,ScriptContext)或 eval(String, ScriptContext)来实现。这样脚本引擎提供者,只需继承这个抽象类并提供这两个函数实现即可。AbstractScriptEngine有一个保护域 context用于保存默认上下文的引用,SimpleScriptContext类被作为 AbstractScriptEngine的默认上下文。关于上下文环境,将在后面进行详细介绍。

  发现和创建脚本引擎

  在前面的两个例子中,ScriptEngine实例都是通过调用 ScriptEngineManager实例的方法返回的,而不是常见的直接通过 new操作新建一个实例。JSR 223 中引入 ScriptEngineManager类的意义就在于,将 ScriptEngine的寻找和创建任务委托给 ScriptEngineManager实例处理,达到对 API 使用者隐藏这个过程的目的,使
Java 应用程序在无需重新编译的情况下,支持脚本引擎的动态替换。通过 ScriptEngineManager类和 ScriptEngineFactory接口即可完成脚本引擎的发现和创建:

  ScriptEngineManager类:自动寻找 ScriptEngineFactory接口的实现类

  ScriptEngineFactory接口:创建合适的脚本引擎实例

  Service Provider

  服务(service)是指那些成为事实上标准的接口,服务提供者(service provider)则提供了这个接口的具体实现。不同的提供者会遵循同样的接口提供实现,客户可以自由选择不同的实现。可以从 Sun 提供的文档 Jar 文件规约中获取有关 Service Provider 更详细的信息。

  ScriptEngineManager类本身并不知道如何创建一个具体的脚本引擎实例,它会依照 Jar 规约中定义的服务发现机制,查找并创建一个合适的 ScriptEngineFactory实例,并通过这个工厂类来创建返回实际的脚本引擎。首先,ScriptEngineManager实例会在当前 classpath 中搜索所有可见的 Jar 包;然后,它会查看每个 Jar 包中的 META -INF/services/ 目录下的是否包含 javax.script.ScriptEngineFactory文件,脚本引擎的开发者会提供在 Jar 包中包含一个 ScriptEngineFactory接口的实现类,这个文件内容即是这个实现类的完整名字;ScriptEngineManager会根据这个类名,创建一个 ScriptEngineFactory接口的实例;最后,通过这个工厂类来实例化需要的脚本引擎,返回给用户。举例来说,第三方的引擎提供者可能升级更新了新版的脚本引擎实现,通过 ScriptEngineManager来管理脚本引擎,无需修改一行
Java 代码就能替换更新脚本引擎。用户只需在 classpath 中加入新的脚本引擎实现(Jar 包的形式),ScriptEngineManager就能通过 Service Provider 机制来自动查找到新版本实现,创建并返回对应的脚本引擎实例供调用。图 2所示时序图描述了其中的步骤:

  图 2. 脚本引擎发现机制时序图

  ScriptEngineFactory接口的实现类被用来描述和实例化 ScriptEngine接口,每一个实现 ScriptEngine接口的类会有一个对应的工厂类来描述其元数据(meta data),ScriptEngineFactory接口定义了许多函数供 ScriptEngineManager查询这些元数据,ScriptEngineManager会根据这些元数据查找需要的脚本引擎,表 2列出了可供使用的函数:

  表 2. ScriptEngineFactory 提供的查询函数

  函数 描述

  String getEngineName() 返回脚本引擎的全称

  String getEngineVersion() 返回脚本引擎的版本信息

  String getLanguageName() 返回脚本引擎所支持的脚本语言的名称

  String getLanguageVersion() 返回脚本引擎所支持的脚本语言的版本信息

  List getExtensions() 返回一个脚本文件扩展名组成的 List,当前脚本引擎支持解析这些扩展名对应的脚本文件

  List getMimeTypes() 返回一个与当前引擎关联的所有 mimetype 组成的 List

  List getNames() 返回一个当前引擎所有名称的 List,ScriptEngineManager可以根据这些名字确定对应的脚本引擎

  通过 getEngineFactories()函数,ScriptEngineManager会返回一个包含当前环境中被发现的所有实现 ScriptEngineFactory接口的具体类,通过这些工厂类中保存的脚本引擎信息检索需要的脚本引擎。第三方提供的脚本引擎实现的 Jar 包中除了包含 ScriptEngine接口的实现类之外,还需要提供 ScriptEngineFactory接口的实现类,以及一个 javax.script.ScriptEngineFactory文件用于指明这个工厂类。这样,
Java 平台就能通过 ScriptEngineManager寻找到这个工厂类,并通过这个工厂类为用户提供一个脚本引擎实例。Java SE 6 默认提供了 JavaScirpt 脚本引擎的实现,如果需要支持其他脚本引擎,需要将它们对应的 Jar 包包含在 classpath 中,比如对于前面 清单 2中的代码,只需在运行程序前将 Groovy 的脚本引擎添加到 classpath 中,然后运行:

  

java RunScript groovy run.groovy



  无需修改一行
Java 代码就能以 Groovy 脚本引擎来运行 Groovy 脚本。在 这里为 Java SE 6 提供了许多著名脚本语言的脚本引擎对 JSR 223 的支持,这些 Jar 必须和脚本引擎配合使用,使得这些脚本语言能被 Java 平台支持。到目前为止,它提供了至少 25 种脚本语言的支持,其中包括了 Groovy、Ruby、Python 等当前非常流行的脚本语言。这里需要再次强调的是,负责创建 ScriptEngine实例的 ScriptEngineFactory实现类对于用户来说是不可见的,ScriptEngingeManager实现负责与其交互,通过它创建脚本引擎。

  脚本引擎的运行上下文

  如果仅仅是通过脚本引擎运行脚本的话,还无法体现出
Java 脚本 API 的优点,在 JSR 223 中,还为所有的脚本引擎定义了一个简洁的执行环境。我们都知道,在 Linux 操作系统中可以维护许多环境变量比如 classpath、path 等,不同的 shell 在运行时可以直接使用这些环境变量,它们构成了 shell 脚本的执行环境。在 javax.script支持的每个脚本引擎也有各自对应的执行的环境,脚本引擎可以共享同样的环境,也可以有各自不同的上下文。通过脚本运行时的上下文,脚本程序就能自由的和 Java 平台交互,并充分利用已有的众多 Java API,真正的站在“巨人”的肩膀上。javax.script.ScriptContext接口和 javax.script.Bindings接口定义了脚本引擎的上下文。

  Bindings 接口:

  继承自 Map,定义了对这些“键-值”对的查询、添加、删除等 Map 典型操作。Bingdings接口实际上是一个存放数据的容器,它的实现类会维护许多“键-值”对,它们都通过字符串表示。
Java 应用程序和脚本程序通过这些“键-值”对交换数据。只要脚本引擎支持,用户还能直接在 Bindings中放置 Java 对象,脚本引擎通过 Bindings不仅可以存取对象的属性,还能调用 Java 对象的方法,这种双向自由的沟通使得二者真正的结合在了一起。

  ScriptContext 接口:

  将 Bindings和 ScriptEngine联系在了一起,每一个 ScriptEngine都有一个对应的 ScriptContext,前面提到过通过 ScriptEnginFactory创建脚本引擎除了达到隐藏实现的目的外,还负责为脚本引擎设置合适的上下文。ScriptEngine通过 ScriptContext实例就能从其内部的 Bindings中获得需要的属性值。ScriptContext接口默认包含了两个级别的 Bindings实例的引用,分别是全局级别和引擎级别,可以通过 GLOBAL_SCOPE和 ENGINE_SCOPE这两个类常量来界定区分这两个 Bindings实例,其中 GLOBAL_SCOPE 从创建它的 ScriptEngineManager获得。顾名思义,全局级别指的是 Bindings里的属性都是“全局变量”,只要是同一个 ScriptEngineMananger返回的脚本引擎都可以共享这些属性;对应的,引擎级别的 Bindings里的属性则是“局部变量”,它们只对同一个引擎实例可见,从而能为不同的引擎设置独特的环境,通过同一个脚本引擎运行的脚本运行时能共享这些属性。

  ScriptContext接口定义了下面这些函数来存取数据:

  表 3. ScriptContext 存取属性函数

  函数 描述

  Object removeAttribute(String name, int scope) 从指定的范围里删除一个属性

  void setAttribute(String name, Object value, int scope) 在指定的范围里设置一个属性的值

  Object getAttribute(String name) 从上下文的所有范围内获取优先级最高的属性的值

  Object getAttribute(String name, int scope) 从指定的范围里获取属性值

  ScriptEngineManager拥有一个全局性的 Bindings实例,在通过 ScriptEngineFactory实例创建 ScriptEngine后,它把自己的这个 Bindings传递给所有它创建的 ScriptEngine实例,作为 GLOBAL_SCOPE。同时,每一个 ScriptEngine实例都对应一个 ScriptContext实例,这个 ScriptContext除了从 ScriptEngineManager那获得的 GLOBAL_SCOPE,自己也维护一个 ENGINE_SCOPE的 Bindings实例,所有通过这个脚本引擎运行的脚本,都能存取其中的属性。除了 ScriptContext可以设置属性,改变内部的 Bindings,
Java 脚本 API 为 ScriptEngineManager和 ScriptEngine也提供了类似的设置属性和 Bindings的 API。

  图 3. Bindings 在 Java 脚本 API 中的分布

  从 图 3中可以看到,共有三个级别的地方可以存取属性,分别是 ScriptEngineManager中的 Bindings,ScriptEngine实例对应的 ScriptContext中含有的 Bindings,以及调用 eval函数时传入的 Bingdings。离函数调用越近,其作用域越小,优先级越高,相当于编程语言中的变量的可见域,即 Object getAttribute(String name)中提到的优先级。从 清单 3这个例子中可以看出各个属性的存取优先级:

  清单 3. 上下文属性的作用域

  



import javax.script.*;

public class ScopeTest {

public static void main(String[] args) throws Exception {

String script=" println(greeting) ";

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("javascript");



//Attribute from ScriptEngineManager

manager.put("greeting", "Hello from ScriptEngineManager");

engine.eval(script);

//Attribute from ScriptEngine

engine.put("greeting", "Hello from ScriptEngine");

engine.eval(script);

//Attribute from eval method

ScriptContext context = new SimpleScriptContext();

context.setAttribute("greeting", "Hello from eval method",

ScriptContext.ENGINE_SCOPE);

engine.eval(script,context);



}

}



  
JavaScript 脚本 println(greeting)在这个程序中被重复调用了三次,由于三次调用的环境不一样,导致输出也不一样,greeting变量每一次都被优先级更高的也就是距离函数调用越近的值覆盖。从这个例子同时也演示了如何使用 ScriptContext和 Bindings这两个接口,在例子脚本中并没有定义 greeting这个变量,但是脚本通过 Java 脚本 API 能方便的存取 Java 应用程序中的对象,输出 greeting相应的值。运行这个程序后,能看到输出为:

  图 4. 程序 ScopeTest 的输出

  除了能在
Java 平台与脚本程序之间的提供共享属性之外,ScriptContext还允许用户重定向引擎执行时的输入输出流:

  表 4. ScriptContext 输入输出重定向

  函数 描述

  void setErrorWriter(Writer writer) 重定向错误输出,默认是标准错误输出

  void setReader(Reader reader) 重定向输入,默认是标准输入

  void setWriter(Writer writer) 重定向输出,默认是标准输出

  Writer getErrorWriter() 获取当前错误输出字节流

  Reader getReader() 获取当前输入流

  Writer getWriter() 获取当前输出流

  清单 4展示了如何通过 ScriptContext将其对应的 ScriptEngine标准输出重定向到一个 PrintWriter中,用户可以通过与这个 PrintWriter连通的 PrintReader读取实际的输出,使
Java 应用程序能获取脚本运行输出,满足更加多样的应用需求。

  清单 4. 重定向脚本输出

  



import java.io.*;

import javax.script.*;

public class Redirectory {

public static void main(String[] args) throws Exception {

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = manager.getEngineByName("javascript");

PipedReader pr = new PipedReader();

PipedWriter pw = new PipedWriter(pr);

PrintWriter writer = new PrintWriter(pw);

engine.getContext().setWriter(writer);

String script = "println('Hello from
JavaScript')";

engine.eval(script); ............................


技术交流 录入:SVN中文技术网[www.svn8.com]
Tags:  
责任编辑:
  • 请文明参与讨论,禁止漫骂攻击。 用户名:新注册) 密码: 匿名:
    评论总数:0 [ 查看全部 ] 网友评论
    关于我们 - 联系我们 - 广告服务 - RSS订阅 - 网站地图 - 返回顶部