Mustang 的脚本引擎
JSR 233 为 Java 设计了一套脚本语言 API。这一套 API 提供了在 Java 程序中调用各种脚本语言引擎的接口。任何实现了这一接口的脚本语言引擎都可以在 Java 程序中被调用。在 Mustang 的发行版本中包括了一个基于 Mozilla Rhino 的 JavaScript 脚本引擎。
Mozilla Rhino
Rhino 是一个纯 Java 的开源的 JavaScript 实现。他的名字来源于 O'Reilly 关于 JavaScript 的书的封面:
Rhino 项目可以追朔到 1997 年,当时 Netscape 计划开发一个纯 Java 实现的 Navigator,为此需要一个 Java 实现的 JavaScript —— Javagator。它也就是 Rhino 的前身。起初 Rhino 将 JavaScript 编译成 Java 的二进制代码执行,这样它会有最好的性能。后来由于编译执行的方式存在垃圾收集的问题并且编译和装载过程的开销过大,不能满足一些项目的需求,Rhino 提供了解释执行的方式。随着 Rhino 开放源代码,越来越多的用户在自己的产品中使用了 Rhino,同时也有越来越多的开发者参与了 Rhino 的开发并做出了很大的贡献。如今 Rhino1.6R2 版本将被包含在 Java SE6 中发行,更多的 Java 开发者将从中获益。
Rhino 提供了如下功能
对 JavaScript 1.5 的完全支持
直接在 Java 中使用 JavaScript 的功能
一个 JavaScript shell 用于运行 JavaScript 脚本
一个 JavaScript 的编译器,用于将 JavaScript 编译成 Java 二进制文件
支持的脚本语言
在 dev.java.net可以找到官方的脚本引擎的实现项目。这一项目基于BSD License ,表示这些脚本引擎的使用将十分自由。目前该项目已对包括 Groovy, JavaScript, Python, Ruby, PHP 在内的二十多种脚本语言提供了支持。这一支持列表还将不断扩大。
在 Mustang 中对脚本引擎的检索使用了工厂模式。首先需要实例化一个工厂 —— ScriptEngineManager。
// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngineManager 将在 Thread Context ClassLoader 的 Classpath 中根据 jar 文件的 META-INF 来查找可用的脚本引擎。它提供了 3 种方法来检索脚本引擎:
// create engine by name
ScriptEngine engine = factory.getEngineByName ("JavaScript");
// create engine by name
ScriptEngine engine = factory.getEngineByExtension ("js");
// create engine by name
ScriptEngine engine = factory.getEngineByMimeType ("application/javascript");
下面的代码将会打印出当前的 JDK 所支持的所有脚本引擎
ScriptEngineManager factory = new ScriptEngineManager();
for (ScriptEngineFactory available : factory.getEngineFactories()) {
System.out.println(available.getEngineName());
}
以下各章节代码将以 JavaScript 为例。
在 Java 中解释脚本
有了脚本引擎实例就可以很方便的执行脚本语言,按照惯例,我们还是从一个简单的 Hello World 开始:
public class RunJavaScript {
public static void main(String[] args){
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName ("JavaScript");
engine.eval("print('Hello World')");
}
}
这段 Java 代码将会执行 JavaScript 并打印出 Hello World。如果 JavaScript 有语法错误将会如何?
engine.eval("if(true){println ('hello')");
故意没有加上”}”,执行这段代码 Java 将会抛出一个 javax.script.ScriptException 并准确的打印出错信息: Exception in thread "main" javax.script.ScriptException:
sun.org.mozilla.javascript.internal.EvaluatorException:
missing } in compound statement (<Unknown source>#1) in <Unknown source>
at line number 1
at ...
如果我们要解释一些更复杂的脚本语言,或者想在运行时改变该脚本该如何做呢?脚本引擎支持一个重载的 eval 方法,它可以从一个 Reader 读入所需的脚本:
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName ("JavaScript");
engine.eval(new Reader("HelloWorld.js"));
如此这段 Java 代码将在运行时动态的寻找 HelloWorld.js 并执行,用户可以随时通过改变这一脚本文件来改变 Java 代码的行为。做一个简单的实验,Java 代码如下: public class RunJavaScript {
public static void main(String[] args) throws FileNotFoundException,
ScriptException, InterruptedException {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName ("JavaScript");
while (true) {
engine.eval(new FileReader("HelloWorld.js"));
Thread.sleep(1000);
}
}
}
HelloWorld.js 内容为简单的打印一个 Hello World: print('Hello World');
运行 RunJavaScript 将会每一秒钟打印一个 Hello World。这时候修改 HelloWorld.js 内容为 print('Hello Tony');
打 印的内容将变为 Hello Tony,由此可见 Java 程序将动态的去读取脚本文件并解释执行。对于这一简单的 Hello World 脚本来说,IO 操作将比直接执行脚本损失 20% 左右的性能(在我的 Think Pad 上),但他带来的灵活性——在运行时动态改变代码的能力,在某些场合是十分激动人心的。
脚本语言与 Java 的通信
ScriptEngine 的 put 方法用于将一个 Java 对象映射成一个脚本语言的变量。现在有一个 Java Class,它只有一个方法,功能就是打印一个字符串 Hello World:
package tony;
public class HelloWorld {
String s = "Hello World";
public void sayHello(){
System.out.println(s);
}
}
那么如何在脚本语言中使用这个类呢?put 方法可以做到:
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class TestPut {
public static void main(String[] args) throws ScriptException {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
HelloWorld hello = new HelloWorld();
engine.put("script_hello", hello);
engine.eval("script_hello.sayHello()");
}
}
首 先我们实例化一个 HelloWorld,然后用 put 方法将这个实例映射为脚本语言的变量 script_hello。那么我们就可以在 eval() 函数中像 Java 程序中同样的方式来调用这个实例的方法。同样的,假设我们有一个脚本函数,它进行一定的计算并返回值,我们在 Java 代码中也可以方便的调用这一脚本:
package tony;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class TestInv {
public static void main(String[] args) throws ScriptException,
NoSuchMethodException {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
String script = "function say(first,second) { print(first +' '+ second); }";
engine.eval(script);
Invocable inv = (Invocable) engine;
inv.invokeFunction("say", "Hello", "Tony");
}
}
在 这个例子中我们首先定义了一个脚本函数 say,它的作用是接受两个字符串参数将他们拼接并返回。这里我们第一次遇到了 ScriptEngine 的两个可选接口之一 —— Invocable,Invocable 表示当前的 engine 可以作为函数被调用。这里我们将 engine 强制转换为 Invocable 类型,使用 invokeFunction 方法将参数传递给脚本引擎。invokeFunction这个方法使用了可变参数的定义方式,可以一次传递多个参数,并且将脚本语言的返回值作为它的返回 值。下面这个例子用JavaScript实现了一个简单的max函数,接受两个参数,返回较大的那个。为了便于断言结果正确性,这里继承了JUnit Testcase,关于JUnit请参考www.junit.org。
package tony;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import junit.framework.TestCase;
public class TestScripting extends TestCase {
public void testInv() throws ScriptException, NoSuchMethodException {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
String script = "function max(first,second) "
+ "{ return (first > second) ?first:second;}";
engine.eval(script);
Invocable inv = (Invocable) engine;
Object obj = inv.invokeFunction("max", "1", "0");
assertEquals("1", obj.toString());
}
}
Invocable 接口还有一个方法用于从一个 engine 中得到一个 Java Interface 的实例,它的定义如下:
<T> T getInterface(Class<T> clasz)
它 接受一个 Java 的 Interface 类型作为参数,返回这个 Interface 的一个实例。也就是说你可以完全用脚本语言来写一个 Java Interface 的所有实现。以下是一个例子。首先定一了个 Java Interface,它有两个简单的函数,分别为求最大值和最小值:
package tony;
public interface MaxMin {
public int max(int a, int b);
public int min(int a, int b);
}
这个 Testcase 用 JavaScript 实现了 MaxMin 接口,然后用 getInterface 方法返回了一个实例并验证了结果。
public void testInvInterface() throws ScriptException,
NoSuchMethodException {
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
String script = "function max(first,second) "
+ "{ return (first > second) ?first:second;}";
script += "function min(first,second) { return (first < second) ?first:second;}";
engine.eval(script);
Invocable inv = (Invocable) engine;
MaxMin maxMin = inv.getInterface(MaxMin.class);
assertEquals(1, maxMin.max(1, 0));
assertEquals(0, maxMin.min(1, 0));
}
脚本的编译执行
到 目前为止,我们的脚本全部都是解释执行的,相比较之下编译执行将会获得更好的性能。这里将介绍 ScriptEngine 的另外一个可选接口 —— Compilable,实现了这一接口的脚本引擎支持脚本的编译执行。下面这个例子实现了一个判断给定字符串是否是 email 地址或者 ip 地址的脚本:
public void testComplie() throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
String script = "var email=/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]"
+ "+(\\.[a-zA-Z0-9_-]+)+$/;";
script += "var ip = /^(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])"
+"(\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])){3}$/;";
script += "if(email.test(str)){println('it is an email')}"
+ "else if(ip.test(str)){println('it is an ip address')}"
+ "else{println('I don\\'t know')}";
engine.put("str", "email@address.tony");
Compilable compilable = (Compilable) engine;
CompiledScript compiled = compilable.compile(script);
compiled.eval();
}
脚 本编译的过程如下:首先将 engine 转换为 Compilable 接口,然后调用 Compilable 接口的 compile 方法得到一个 CompiledScript 的实例,这个实例就代表一个编译过的脚本,如此用 CompiledScript 的 eval 方法即为调用编译好的脚本了。在我的 Think Pad 上,这段代码编译后的调用大约比直接调用 engine.eval 要快 3-4 倍。随着脚本复杂性的提升,性能的提升会更加明显。
脚本上下文与绑定
真正将脚本语言与 Java 联系起来的不是 ScriptEngine,而是 ScriptContext,它作为 Java 与 ScriptEngine 之间的桥梁而存在。
一 个 ScriptEngine 会有一个相应的 ScriptContext,它维护了一个 Map,这个 Map 中的每个元素都是脚本语言对象与 Java 对象之间的映射。同时这个 Map 在我们的 API 中又被称为 Bindings。一个 Bindings 就是一个限定了 key 必须为 String 类型的 Map —— Map<String, Object>。所以一个 ScriptContext 也会有对应的一个 Bindings,它可以通过 getBindings 和 setBindings 方法来获取和更改。
一个 Bindings 包括了它的 ScriptContext 中的所有脚本变量,那么如何获取脚本变量的值呢?当然,从 Bindings 中 get 是一个办法,同时 ScriptContext 也提供了 getAttribute 方法,在只希望获得某一特定脚本变量值的时候它显然是十分有效的。相应地 setAttribute 和 removeAttribute 可以增加、修改或者删除一个特定变量。
在 ScriptContext 中存储的所有变量也有自己的作用域,它们可以是 ENGINE_SCOPE 或者是 GLOBAL_SCOPE,前者表示这个 ScriptEngine 独有的变量,后者则是所有 ScriptEngine 共有的变量。例如我们执行 engine.put(key, value) 方法之后,这时便会增加一个 ENGINE_SCOPE 的变量,如果要定义一个 GLOBAL_SCOPE 变量,可以通过 setAttribute(key, value, ScriptContext.GLOBAL_SCOPE) 来完成。
此外 ScriptContext 还提供了标准输入和输出的重定向功能,它可以用于指定脚本语言的输入和输出。
回页首
在 JavaScript 中使用 Java 高级特性
这一部分不同于前述内容,将介绍 JavaScript引擎 —— Rhino 独有的特性。
使用 Java 对象
前 面的部分已经介绍过如何在 JavaScript 中使用一个已经实例化的 Java 对象,那么如何在 JavaScript 中去实例化一个 Java 对象呢?在 Java 中所有 Class 是按照包名分层次存放的,而在 JavaScript 没有这一结构,Rhino 使用了一个巧妙的方法实现了对所有 Java 对象的引用。Rhino 中定义了一个全局变量—— Packages,并且它的所有元素也是全局变量,这个全局变量维护了 Java 类的层次结构。例如 Packages.java.io.File 引用了 Java 的 io 包中 File 对象。如此一来我们便可以在 JavaScript 中方便的使用 Java 对象了,new 和 Packages 都是可以被省略的:
//The same as: var frame = new Packages.java.io.File("filename");
var frame = java.io.File("filename");
我们也可以像 Java 代码中一样把这个对象引用进来:
importClass (java.io.File);
var file = File("filename");
如果要将整个包下的所有类都引用进来可以用 importPackage:
importPackage(java.io);
如果只需要在特定代码段中引用某些包,可以使用 JavaImporter 搭配 JavaScript 的 with 关键字,如:
var MyImport = JavaImporter(java.io.File);
with (MyImport) {
var myFile = File("filename");
}
用户自定义的包也可以被引用进来,不过这时候 Packages 引用不能被省略:
importPackage(Packages.tony);
var hello = HelloWorld();
hello.sayHello();
注意这里只有 public 的成员和方法才会在 JavaScript 中可见,例如对 hello.s 的引用将得到 undefined。下面简单介绍一些常用的特性:
使用 Java 数组
需要用反射的方式构造:
var a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
对于大部分情况,可以使用 JavaScript 的数组。将一个 JavaScript 的数组作为参数传递给一个 Java 方法时 Rhino 会做自动转换,将其转换为 Java 数组。
实现一个 Java 接口
除了上面提到的 Invocable 接口的 getInterface 方法外,我们也可以在脚本中用如下方式:
//Define a JavaScript Object which has corresponding method
obj={max:function(a,b){return (a > b) ?a:b;}};
//Pass this object to an Interface
maxImpl=com.tony.MaxMin(obj);
//Invocation
print (maxImpl.max(1,2));
如果接口只有一个方法需要实现,那么在 JavaScript 中你可以传递一个函数作为参数:
function func(){
println("Hello World");
}
t=java.lang.Thread(func);
t.start();
对于 JavaBean 的支持
Rhino 对于 JavaBean 的 get 和 is 方法将会自动匹配,例如调用 hello.string,如果不存在 string 这个变量,Rhino 将会自动匹配这个实例的 isString 方法然后再去匹配 getString 方法,若这两个方法均不存在才会返回 undefined。
回页首
命令行工具 jrunscript
在 Mustang 的发行版本中还将包含一个脚本语言的的命令行工具,它能够解释所有当前 JDK 支持的脚本语言。同时它也是一个用来学习脚本语言很好的工具。你可以在http://java.sun.com/javase/6/docs/technotes/tools/share/jrunscript.html找到这一工具的详细介绍。
文章出处:DIY部落(http://www.diybl.com/course/1_web/javascript/Javascriptxl/2008921/144278.html)
相关推荐
《深入浅出Rhino:Java与JS互操作》是一本专注于探讨如何在Java环境中使用Rhino JavaScript引擎进行交互的书籍。Rhino是Mozilla基金会开发的一个开源JavaScript解释器,它完全用Java编写,使得JavaScript能够在Java...
Java运行JavaScript脚本引擎Rhino是一个强大的工具,它允许Java应用程序执行JavaScript代码并利用JavaScript的灵活性和动态性。Rhino是Mozilla基金会开发的一个开源项目,它完全用Java编写,因此可以无缝集成到Java...
它不仅能够解析和执行JavaScript脚本,而且还可以作为Java程序的一部分,允许开发者直接在Java环境中使用JavaScript。Rhino是Java Scripting API (JSR223) 的一部分,并且在Java SE 6(代号为Mustang)及以后的版本...
3. **Java中使用JavaScript**:Rhino使得开发者能够在Java代码中直接调用和执行JavaScript脚本,增加了代码的多样性。 4. **JavaScript shell**:Rhino提供了一个shell环境,用于直接运行JavaScript脚本。 5. **...
Rhino以其高效的性能和强大的功能,成为Java开发者在服务器端执行JavaScript脚本的首选工具。 Rhino的核心特性在于其对ECMAScript规范的全面支持,这使得JavaScript代码能够在Java环境中无缝执行。1.7.2版本发布于...
总结,"org.mozilla.javascript-1.7.2.jar.zip"是Mozilla Rhino引擎的一个重要版本,它在Java平台上实现了高性能的JavaScript解释和执行,为开发者提供了丰富的功能和广泛的适用场景。无论是Web开发、服务器脚本,...
这个API在Java 6版本中得到了显著增强,为开发者提供了在Java程序中嵌入和执行脚本引擎的能力,例如JavaScript、Groovy或Rhino等。通过Java脚本API,开发者可以利用脚本语言的灵活性和简洁性,同时保持Java的系统级...
5. **脚本编写和执行**:Rhino提供了ScriptEngine接口,这是Java Scripting API (JSR 223)的一部分,使得开发者可以方便地在Java程序中执行JavaScript代码,处理脚本编写和执行的各种需求。 6. **Rhino的API**:...
java中执行javascript脚本需要用到的引擎工具,jdk1.6已自带,但1.5及以下还是需要的
Java语言在处理JavaScript脚本时,常常会借助于一些库来实现,其中一个著名的库就是Rhino。Rhino是由Mozilla开发的一个开源的JavaScript引擎,它完全用Java编写,能够将JavaScript代码编译为Java字节码,从而使得...
JSR 223是Java的一个规范,允许在Java应用程序中嵌入和执行各种脚本语言。它提供了一个统一的接口,使得Java开发者可以方便地引入和使用不同的脚本引擎,比如JavaScript、Groovy或Python。JSR 223包含以下关键组件:...
在Java中使用JavaScript,官方指南主要介绍的是在Java平台标准版中嵌入和使用JavaScript脚本语言的官方指导。这通常意味着使用Java代码来调用JavaScript,并且允许JavaScript在Java应用程序内部执行。具体的知识点...
通过上述步骤,我们可以创建一个功能完备的JavaScript脚本调试器,利用Rhino的强大力量和JavaFX的出色界面设计,为开发者提供了一个在Java环境中调试JavaScript的高效工具。这样的工具对于开发复杂的JavaScript应用...
Rhino是Mozilla开发的一个开源JavaScript引擎,它允许JavaScript在Java平台上运行,而Rhino Shell则是一个命令行工具,可以用来交互式地执行JavaScript脚本。Debugger则是用于检查和优化代码的工具,这对于理解和...
Gino,全称为“Gradle JavaScript Rhino插件”,它为Java或Groovy应用程序提供了一个简洁的接口,用于启动和执行JavaScript程序。通过集成Gradle构建系统,Gino使得JavaScript与Java/Groovy项目的集成变得更为简单,...
在描述中提到的博客链接(已省略)可能会详细解释如何设置和执行这种测试,包括如何创建JavaScript测试脚本,如何在Rhino中运行它们,以及如何将测试结果与JUnit对接以展示在GUI上。这个过程可能包括以下步骤: 1. ...
这对于那些需要在服务器端或者桌面应用中运行JavaScript脚本的Java开发者来说,是一个非常重要的工具。 在Java 8中,Nashorn引擎主要通过以下两个接口提供服务: 1. `ScriptEngine`:这是Java的`javax.script`包中...
这个标签允许我们在构建过程中执行JavaScript,使用的引擎通常是Rhino,这是一个由Mozilla开发的JavaScript引擎,能够运行在Java环境中。通过这种方式,我们可以编写动态脚本来处理那些内建任务无法完成的工作,比如...