- 浏览: 55579 次
- 性别:
- 来自: 成都
最新评论
-
jasen0429:
好多都解压有问题!有可以用的下载资源!谢谢!
Grails入门 -
AJian759447583:
楼主成都人呐,好标准的口音!
Grails入门 -
xuqiao2009:
是不是还有下班部分啊
Grails入门 -
xuqiao2009:
代码在哪里下载啊
Grails入门 -
AJian759447583:
下载下来的压缩包要重命名为1.rar,2.rar.....才能 ...
Grails入门
脚本语言与 Java
假设我们有一个简单的需求,察看一份文档中 5 个字母组成的单词的个数。用 Java 一般实现如下:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Find5Words {
public static void main(String[] args) throws IOException {
String result = "";
String line = null;
int num = 0;
FileReader fr = new FileReader("filename");
BufferedReader br = new BufferedReader(fr);
while ((line = br.readLine()) != null) {
result += line;
}
br.close();
fr.close();
String[] s = result.split(" ");
for (int i = 0; i < s.length; i++) {
if (s[i].matches("^\\w{5}$")) {
num++;
}
}
System.out.println(num);
}
}
再看看 Perl 语言实现同样功能的代码:
open FILE, "<filename ";
while (<FILE>) {
for (split) {
$num++ if /^\w{5}$/
}
}
print $num;
那么有没有一种优雅的方式将 Java 与脚本语言结合呢,在今年秋季即将发布的 Java SE6(代号 Mustang)中,这将成为现实。
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找到这一工具的详细介绍。
结束语
脚本语言牺牲执行速度换来更高的生产率和灵活性。随着计算机性能的不断提高,硬件价格不断下降,可以预见的,脚本语言将获得更广泛的应用。在 JavaSE 的下一个版本中加入了对脚本语言的支持,无疑将使 Java 程序变得更加灵活,也会使 Java 程序员的工作更加有效率。参考资料
JSR 233 主页http://www.jcp.org/en/jsr/detail?id=223 你可以在这里找到关于这个 Request 的详细信息。Mozilla Rhino 项目主页http://www.mozilla.org/rhino/ 这里有完善的项目文档,并且可以下载到该项目的最新发行版本以及源代码。https://scripting.dev.java.net/是官方脚本引擎项目的主页,在这里可以查看到已经对哪些脚本引擎提供了支持。并且可以找到相应脚本引擎的网页链接。http://www.opensource.org/licenses/ 在这里可以找到关于 Open source license 的详细信息。http://www.junit.org/ 是 Junit 的官方站点http://java.sun.com/javase/6/docs/是 Java SE6 的在线文档
关于作者:
吴玥颢,目前就职于 IBM 中国开发中心 Harmony 开发团队。 除了对 Java 和脚本语言的热爱之外,他的兴趣还包括哲学、神话、历史与篮球。此外他还是个电脑游戏高手。您可以通过wuyuehao@cn.ibm.com联系到他
假设我们有一个简单的需求,察看一份文档中 5 个字母组成的单词的个数。用 Java 一般实现如下:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Find5Words {
public static void main(String[] args) throws IOException {
String result = "";
String line = null;
int num = 0;
FileReader fr = new FileReader("filename");
BufferedReader br = new BufferedReader(fr);
while ((line = br.readLine()) != null) {
result += line;
}
br.close();
fr.close();
String[] s = result.split(" ");
for (int i = 0; i < s.length; i++) {
if (s[i].matches("^\\w{5}$")) {
num++;
}
}
System.out.println(num);
}
}
再看看 Perl 语言实现同样功能的代码:
open FILE, "<filename ";
while (<FILE>) {
for (split) {
$num++ if /^\w{5}$/
}
}
print $num;
那么有没有一种优雅的方式将 Java 与脚本语言结合呢,在今年秋季即将发布的 Java SE6(代号 Mustang)中,这将成为现实。
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找到这一工具的详细介绍。
结束语
脚本语言牺牲执行速度换来更高的生产率和灵活性。随着计算机性能的不断提高,硬件价格不断下降,可以预见的,脚本语言将获得更广泛的应用。在 JavaSE 的下一个版本中加入了对脚本语言的支持,无疑将使 Java 程序变得更加灵活,也会使 Java 程序员的工作更加有效率。参考资料
JSR 233 主页http://www.jcp.org/en/jsr/detail?id=223 你可以在这里找到关于这个 Request 的详细信息。Mozilla Rhino 项目主页http://www.mozilla.org/rhino/ 这里有完善的项目文档,并且可以下载到该项目的最新发行版本以及源代码。https://scripting.dev.java.net/是官方脚本引擎项目的主页,在这里可以查看到已经对哪些脚本引擎提供了支持。并且可以找到相应脚本引擎的网页链接。http://www.opensource.org/licenses/ 在这里可以找到关于 Open source license 的详细信息。http://www.junit.org/ 是 Junit 的官方站点http://java.sun.com/javase/6/docs/是 Java SE6 的在线文档
关于作者:
吴玥颢,目前就职于 IBM 中国开发中心 Harmony 开发团队。 除了对 Java 和脚本语言的热爱之外,他的兴趣还包括哲学、神话、历史与篮球。此外他还是个电脑游戏高手。您可以通过wuyuehao@cn.ibm.com联系到他
评论
1 楼
xiaojiit
2008-11-18
<p>不错,收藏了! <br/><a href='http://www.sharejava.cn/' title='http://www.sharejava.cn/'>http://www.sharejava.cn/</a></p>
<p> </p>
<p> </p>
发表评论
-
android sdk
2010-06-21 23:05 867android的开发页面被和谐掉了。。找了个代理下,花了半个多 ... -
从JAVA过渡到Groovy心得(上)
2009-11-13 11:40 1868groovy本其官方形容成JVM上的天使语言,确实也挺不错,开 ... -
SnmpHibernate Developer Guide(1.0Beta)
2009-08-25 09:37 1826Overview SnmpHibernate is a MI ... -
JMS在glassfish中的使用(1)——JMS入门
2009-08-22 11:01 2461通常使用JMS,我想要做的事是,让客户端应用(桌面应用),通过 ... -
几个谜题,深入的了解java
2009-08-05 11:50 857此文转载于:http://zangxt.iteye.com/b ... -
JAVA结合JAVASCRIPT的应用实例
2009-08-05 11:24 1272import java.io.FileNotFoundEx ... -
JXPATH指南
2008-11-24 14:43 1090使用JXPath查询Java对象 —使用XPath表达式语言查 ... -
JFX 学习笔记 八 反射
2008-11-24 10:46 814反射关键字class System.ou ... -
JFX学习笔记 七 增量式求值和懒惰求值
2008-11-24 09:14 934/* * Main.fx * * Created on ... -
JFX 学习笔记 七 触发器
2008-11-21 13:59 837想想JFX的用途就知道为什么要在语法中实现触发器这样的功能了, ... -
JFX学习笔记 六 类
2008-11-21 11:59 912终于看到类了,还真是累.... JavaFX 中声明类的语法 ... -
JFX学习笔记 五 重修语法——表达式 (2)
2008-11-21 11:22 824JFX到底是一个什么品种,看完下面的描述各位就清楚了。 Jav ... -
JFX学习笔记 五 重修语法——表达式
2008-11-21 10:53 942学习笔记四里面各位也都看到了,在语法上JFX与JAVA有许多地 ... -
JFX学习笔记 三 重修语法之数组(2)
2008-11-21 10:11 941网速质素不高..... 继续数组吧,之前我在使用时出现了一些错 ... -
jdk6新特性——http server
2008-11-21 08:52 5713启动下面代码后,浏览器中访问:http://127.0.0.1 ... -
JFX学习笔记 四 语法对比
2008-11-21 08:37 792未完继续.... -
JFX学习笔记 三 重修语法之数组
2008-11-20 09:49 957相当郁闷,本来写好的,结果遭遇PC重启,又....只能当再次复 ... -
JFX学习笔记 二 重修语法
2008-11-19 17:01 1074[size=large]首先准备好材料,以便后面开始JFX大餐 ... -
JFX学习笔记 一 前因后果
2008-11-19 15:50 1021又是一个新东东真是让人兴奋又头痛 。JAVA已经被“世俗”影 ... -
JNLP JAVA应用发布方式
2008-11-19 14:05 1824jnlp(Java网络加载协议)原来很简单 Java Net ...
相关推荐
Java是一种广泛使用的面向对象的编程语言,而脚本语言则以它们的轻量级、解释执行和快速开发特性而闻名。两者在软件开发领域中各有所长,常常被用来解决不同的问题。本压缩包文件“java与脚本语言.rar”可能包含关于...
* 在Java中使用脚本语言JavaScript。 * * @author LeiWen@FansUnion.cn,http://FansUnion.cn, * http://blog.csdn.net/FansUnion * * QQ:240370818 参考资料:...
本文探讨了Java和脚本语言的整合与应用,讨论了脚本语言的开发特点和Java与脚本语言的整合理由、整合方法和注意事项,并对Java与其他语言的互操作性进行了研究,为程序开发者提供了一个合适的选择来平衡利用Java和...
根据给定的信息,“Java脚本编程:语言、框架与模式”这一主题包含了三个核心部分:Java作为一门编程语言的基础知识、流行的Java开发框架以及在Java编程中常用的软件设计模式。 ### Java编程语言 #### 1. Java语言...
JavaSprint是一种基于Java的脚本语言,常用于快速开发Web应用程序。它的设计目标是简化Web界面的构建,提供一种更高效、更易读的语法,使得开发者可以更专注于业务逻辑,而不是繁琐的低级编程任务。在"javasprint...
JSAPI是Java平台标准版(Java SE)的一部分,它允许开发者在Java程序中嵌入并执行脚本语言。JSAPI通过提供ScriptEngine接口,使得开发者能够轻松地调用不同脚本引擎,如JavaScript、Groovy或Rhino,执行脚本代码。...
需要注意的是,这里提到的“Java脚本编程”可能是指与Java相关的脚本编程技术,但在实际中,Java本身并不是一种脚本语言,而是一种面向对象的编程语言。不过,Java生态系统中有多种脚本语言可以运行在Java平台上,...
网页JS脚本注入,突破网页本地脚本验证方法实例 JS脚本注入是一种常见的网页攻击手法,通过注入恶意脚本来控制网页的行为。在这个实例中,我们将展示如何使用JS脚本注入来突破网页本地脚本验证方法,跳过验证码,并...
总之,Java调用Python脚本是一个实用的技巧,它允许开发者结合两种语言的优点,提高代码的可维护性和复用性。通过理解上述概念和实践,你可以灵活地在Java项目中嵌入Python功能。如果有任何疑问,可以参考解压后的...
java的windows启动脚本
讲解了Java脚本引擎API的使用方法,包括如何加载和执行动态脚本语言,并探讨了其在Java应用程序中的应用场景。 #### 国际化与本地化 介绍了Java中处理国际化和本地化的方法,包括资源束、格式化日期时间等,并讨论...
7. **新的 Nashorn JavaScript引擎**:JDK 8包含了一个内置的JavaScript引擎,允许Java程序直接执行JavaScript代码,促进了Java与其他脚本语言的交互。 8. **并发改进**:JDK 8对并发工具进行了优化,例如`ForkJoin...
这是一个java项目打包部署以后的启动脚本,配置了启动参数等
通过这个接口,你可以编写Java代码来调用和执行脚本语言的函数,或者将Java对象暴露给脚本环境。例如,`eval()`方法用于执行一个字符串形式的脚本,`put()`和`get()`方法则用于在Java和脚本环境中交换数据。 2. **...
Java 脚本 API,也称为 Java Scripting API,是Java平台标准版(Java SE)的一部分,它允许Java应用程序在运行时执行脚本语言代码。这个API在Java 6版本中得到了显著增强,为开发者提供了在Java程序中嵌入和执行脚本...
该项目是一款基于Java语言开发的自定义脚本语言解释器设计源码,总计包含85个文件,其中包含75个Java源文件,以及2个Git忽略文件、2个XML配置文件、2个Gradle文件、1个Markdown文件、1个jar文件和1个属性文件。...
在IT领域,Java是一种广泛使用的编程语言,而Shell脚本则是在Linux环境下广泛使用的一种命令语言和脚本解释器。在某些情况下,Java程序需要调用Shell脚本执行特定的操作,比如访问Linux系统命令或者自动化执行一些...
9. **脚本引擎支持**:Java 6通过`javax.script`包添加了对脚本语言(如JavaScript、Groovy等)的支持,可以在Java环境中直接运行和集成这些脚本。 10. **JDBC 4.0**:Java Database Connectivity (JDBC) API在Java...