Java Scripting Programmer’s Guide

Java 6 引入了 script 功能, 使得很多 script language 可以运行于 Java VM 中. 这会给我们的很多应用带来很有趣又很有用的特性, 比如将一些(需动态改变的)业务逻辑交给这些 script language 来处理. 本文翻译自 <Java Scripting Programmer’s Guide>.

———————————————————

Table Of  Content

Who is the Java Scripting API For?

脚本语言的一些有用特性有:

JavaTM Scripting API 是一个独立于脚本语言的框架, 用于在 Java 代码中使用脚本引擎. 通过使用 Java Scripting API, 使得通过 Java 语言编写 可定制的/可扩展的 应用程序成为可能, 终端用户可以自行选择用于定制目的的脚本语言. Java 应用程序开发人员在开发期间不需选择扩展语言. 如果你使用 JSR-223 API 编写你的程序, 那么你的用户就可以使用任何 JSR-223 兼容的脚本语言.

Scripting Package

Java Scripting 功能在 javax.script package 内. 这是一个相对来说比较小的, 简单的 API. Scripting API 的起点是 ScriptEngineManager class. ScriptEngineManager 对象能够通过 JAR 文件服务查找机制找到脚本引擎. 它也能实例化 ScriptEngine 对象用于解释使用特定脚本语言编写的脚本. 使用 scripting API 的最简单的方式是:

  1. 创建一个 ScriptEngineManager 对象.
  2. 从该 manager 得到一个 ScriptEngine 对象.
  3. 使用 ScriptEngine’s eval 方法使脚本可用(evaluate).

现在, 是时候看看一些例子代码了. 尽管这不是强制性的, 但阅读这些例子对于了解一些JavaScript来说可能是很有用的.

Examples

“Hello, World”

ScriptEngineManager 实例上, 我们通过使用 getEngineByName 方法请求得到一个 JavaScript engine 实例. 在脚本引擎上, 调用 eval 方法去执行给出的字符串作为 JavaScript 代码! 为简洁起见, 在这个例子和接下来的例子中, 我们不会展示如何进行异常处理. 从 javax.script API 抛出的异常有 checked 和 runtime exceptions. 无需多言, 你得自己适当地处理这些异常.

import javax.script.*;
public class EvalScript {
    public static void main(String[] args) throws Exception {
        // create a script engine manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // create a JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // evaluate JavaScript code from String
        engine.eval("print('Hello, World')");
    }
}

Evaluating a Script File

在这个例子里, 我们调用 eval 方法, 该方法接收 java.io.Reader 作为输入源. 被给定 reader 所读取的脚本将被执行. 通过这种方式, 使得执行来自文件, URLs 和通过将相关输入流对象包装为 reader 作为来源的脚本成为可能.

import javax.script.*;
public class EvalFile {
    public static void main(String[] args) throws Exception {
        // create a script engine manager
        ScriptEngineManager factory = new ScriptEngineManager();
        // create JavaScript engine
        ScriptEngine engine = factory.getEngineByName("JavaScript");
        // evaluate JavaScript code from given file - specified by first argument
        engine.eval(new java.io.FileReader(args[0]));
    }
}

假设我们所拥有的名为 “test.js” 的文件具有如下文本内容:

println("This is hello from test.js");

我们使用如下方式运行

java EvalFile test.js

Script Variables

当向你的 Java 程序中嵌入脚本引擎和脚本时, 你可能想要将你的应用程序中的对象暴露为脚本中的全局变量. 本例演示了怎样将你的应用程序对象暴露为脚本中的全局变量. 我们在程序中创建了一个 java.io.File, 然后将其暴露为名为 “file” 的全局变量. 脚本能够访问到该变量 – 例如, 调用该变量上的 public 方法. 需要注意的是访问 Java 对象, 方法和字段的语法取决于脚本语言. JavaScript 支持大部分 “固有的(natural)” 的 Java-like 语法..

public class ScriptVars {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        File f = new File("test.txt");
        // expose File object as variable to script
        engine.put("file", f);

        // evaluate a script string. The script accesses "file"
        // variable and calls method on it
        engine.eval("print(file.getAbsolutePath())");
    }
}

Invoking Script Functions and Methods

有时你可能想重复调用某个特定脚本函数 – 如, 你的程序菜单函数可能会由一个脚本来实现. 在你的菜单的事件处理器上, 你可能要调用某个特定脚本函数. 下面的例子演示了如何从 Java 代码里调用特定脚本功能..

import javax.script.*;

public class InvokeScriptFunction {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // JavaScript code in a String
        String script = "function hello(name) { print('Hello, ' + name); }";
        // evaluate script
        engine.eval(script);

        // javax.script.Invocable is an optional interface.
        // Check whether your script engine implements or not!
        // Note that the JavaScript engine implements Invocable interface.
        Invocable inv = (Invocable) engine;

        // invoke the global function named "hello"
        inv.invokeFunction("hello", "Scripting!!" );
    }
}

如果你的脚本语言是基于对象的 (像 JavaScript) 或者面向对象的, 那么你可以从一个脚本对象上调用一个脚本方法.

import javax.script.*;

public class InvokeScriptMethod {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // JavaScript code in a String. This code defines a script object 'obj'
        // with one method called 'hello'.
        String script = "var obj = new Object(); obj.hello = function(name) { print('Hello, ' + name); }";
        // evaluate script
        engine.eval(script);

        // javax.script.Invocable is an optional interface.
        // Check whether your script engine implements or not!
        // Note that the JavaScript engine implements Invocable interface.
        Invocable inv = (Invocable) engine;

        // get script object on which we want to call the method
        Object obj = engine.get("obj");

        // invoke the method named "hello" on the script object "obj"
        inv.invokeMethod(obj, "hello", "Script Method !!" );
    }
}

Implementing Java Interfaces by Scripts

有时使用脚本函数或者方法去实现一个 Java 接口是很方便的, 而不是从 Java 代码中调用特定脚本函数. 同样, 通过使用接口, 我们能够避免不得不在许多地方使用 javax.script API. 我们可以获得一个接口实现者的对象并将其传递给 Java APIs. 下面这个例子演示了如何使用脚本实现 java.lang.Runnable 接口.

import javax.script.*;

public class RunnableImpl {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // JavaScript code in a String
        String script = "function run() { println('run called'); }";

        // evaluate script
        engine.eval(script);

        Invocable inv = (Invocable) engine;

        // get Runnable interface object from engine. This interface methods
        // are implemented by script functions with the matching name.
        Runnable r = inv.getInterface(Runnable.class);

        // start a new thread that runs the script implemented
        // runnable interface
        Thread th = new Thread(r);
        th.start();
    }
}

如果你的脚本语言是基于对象或者面向对象的, 那么使用脚本对象上的脚本方法实现一个 Java 接口是可能的. 这避免了不得不为接口方法调用脚本全局函数. 脚本对象能够存储与接口实现者相关的”状态”.

import javax.script.*;

public class RunnableImplObject {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        // JavaScript code in a String
        String script = "var obj = new Object(); obj.run = function() { println('run method called'); }";

        // evaluate script
        engine.eval(script);

        // get script object on which we want to implement the interface with
        Object obj = engine.get("obj");

        Invocable inv = (Invocable) engine;

        // get Runnable interface object from engine. This interface methods
        // are implemented by script methods of object 'obj'
        Runnable r = inv.getInterface(obj, Runnable.class);

        // start a new thread that runs the script implemented
        // runnable interface
        Thread th = new Thread(r);
        th.start();
    }
}

Multiple Scopes for Scripts

script variables 例子里, 我们已经看到如何将应用程序对象暴露为脚本全局变量. 也能够为脚本暴露多个全局”域(scopes)”. 单个域(scope)是 javax.script.Bindings 的一个实例. 这个接口派生自 java.util.Map<String, Object>. 一个域是一个 name-value 对的集合, 其中 name 可以是任何非空, 非 null 的字符串. javax.script.ScriptContext 接口支持多个域. 一个 script context 支持一个或多个域, 每个域都有与之相关联的 Bindings. 默认的, 每个脚本引擎都有一个默认 script context. 默认 script context 有至少一个域叫做 “ENGINE_SCOPE”. 一个 script context 所支持的各种不同的域可通过 getScopes 方法查找到.

import javax.script.*;

public class MultiScopes {
    public static void main(String[] args) throws Exception {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("JavaScript");

        engine.put("x", "hello");
        // print global variable "x"
        engine.eval("println(x);");
        // the above line prints "hello"

        // Now, pass a different script context
        ScriptContext newContext = new SimpleScriptContext();
        Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);

        // add new variable "x" to the new engineScope
        engineScope.put("x", "world");

        // execute the same script - but this time pass a different script context
        engine.eval("println(x);", newContext);
        // the above line prints "world"
    }
}

JavaScript Script Engine

Sun 的 JDK 6 实现里捆绑了基于 Mozilla Rhino 的 JavaScript 脚本引擎. 它是基于 Mozilla Rhino version 1.6R2. 大部分 Rhino 实现都包含进来了. 由于内存占用(footprint)和安全原因, 一些组件被排除在外:

  1. JavaScript-到-字节码 编译(也叫 “优化”). 该特性依赖于一个类生成库. 移除该特性意味着 JavaScript 总是会被解释. 移除该特性不会影响脚本执行因为优化是透明的.
  2. Rhino 的 JavaAdapter 已被删除. JavaAdapter 能够让一个 Java class 被 JavaScript 扩展(继承), Java 接口由 JavaScript 实现. 该特性也需要一个类生成库. 我们已经将 Rhino’s JavaAdapter 替换为 Sun 的 JavaAdapter 实现. 在 Sun 的实现里, 一个 JavaScript 对象只能实现一个 Java 接口(Only a single interface may be implemented by a JavaScript object). 例如, 下面这段代码运行符合预期.
           var v = new java.lang.Runnable() {
                        run: function() { print('hello'); }
                   }
           v.run();

    大部分情形下, JavaAdapter 用于通过 Java 匿名类相似的语法去实现单一接口. 使用 JavaAdapter 去继承一个 Java 类或者去实现多个接口的用法是非常罕见的.

  3. E4X (ECMAScript for XML – ECMA Standard 357) 已被排除在外. 在 JavaScript 代码里使用 XML 会导致语法错误. 需注意的是在 ECMAScript 标准里 E4X 支持是可选的 – 实现可以忽略对 E4X 的支持, 但仍是一个遵从标准的 ECMAScript 实现.
  4. Rhino 命令行工具 (Rhino shell, debugger etc.) 没有包含进来. 但是, 你可以使用 jrunscript 作为替代.

JavaScript to Java Communication

对于大部分地方, 访问 Java 类, 对象和方法是很直观的. 从 JavaScript 访问特殊的字段和方法跟从 Java 访问是一样方式的. We highlight important aspects of JavaScript Java access here. 更详细的情况, 请参考 http://www.mozilla.org/rhino/scriptjava.html. 下面的例子是访问 Java 的 JavaScript 代码片段. 这一章节需要有 JavaScript 知识. 如果你打算使用其它 JSR-223 脚本语言, 那么可以略过本章节.

Importing Java Packages, Classes

内置函数 importPackage and importClass 可用于导入 Java packages 和 classes.

// Import Java packages and classes
// like import package.*; in Java
importPackage(java.awt);
// like import java.awt.Frame in Java
importClass(java.awt.Frame);
// Create Java Objects by "new ClassName"
var frame = new java.awt.Frame("hello");
// Call Java public methods from script
frame.setVisible(true);
// Access "JavaBean" properties like "fields"
print(frame.title);

Packages 全局变量可以用于访问 Java packages. 例如: Packages.java.util.Vector, Packages.javax.swing.JFrame. 请注意, “java” 是 “Packages.java” 的简写方式. 还有其它等价的 javax, org, edu, com, net 前缀的简写方式, 因此实际上所有 JDK 平台类能够不带 “Packages” 前缀而访问.

注意, java.lang 默认并没有导入(不像Java), 因为这会导致与 JavaScript 内置对象, Boolean, Math 等等产生冲突.

importPackage and importClass 函数 “玷污(pollute)” 了 JavaScript 的全局变量域. 为了避免这种情况, 你可以使用 JavaImporter.

// create JavaImporter with specific packages and classes to import

var SwingGui = new JavaImporter(javax.swing,
                            javax.swing.event,
                            javax.swing.border,
                            java.awt.event);
with (SwingGui) {
    // within this 'with' statement, we can access Swing and AWT
    // classes by unqualified (simple) names.

    var mybutton = new JButton("test");
    var myframe = new JFrame("test");
}

Creating and Using Java Arrays

尽管创建一个 Java 对象跟在 Java 中一样, 但要在 JavaScript 中创建 Java 数组我们需要明确使用 Java 反射. 但是一旦完成了创建, 访问数组中的元素或者长度跟在 Java 中访问方式是一样的. 同样, 当一个 Java 方法期待一个 Java 数组时一个脚本数组也能够被用在这个 Java 方法上(自动转换). 因此在大部分情况下, 我们不必明确创建 Java 数组.

// create Java String array of 5 elements
var a = java.lang.reflect.Array.newInstance(java.lang.String, 5);

// Accessing elements and length access is by usual Java syntax
a[0] = "scripting is great!";
print(a.length);

Implementing Java Interfaces

在 JavaScript 中可以使用类似 Java 匿名类的语法去实现一个 Java 接口:

var r  = new java.lang.Runnable() {
    run: function() {
        print("running...\n");
    }
};

// "r" can be passed to Java methods that expect java.lang.Runnable
var th = new java.lang.Thread(r);
th.start();

当一个接口只有一个方法声明时, 你可以直接传递一个脚本函数.(自动转换)

function func() {
     print("I am func!");
}

// pass script function for java.lang.Runnable argument
var th = new java.lang.Thread(func);
th.start();

Overload Resolution

Java 方法能够通过参数类型进行重载. 在 Java 中, 重载解析发生在编译期(由javac完成). 当从一个脚本调用 Java 方法时, 脚本解释器/编译器 需要选择合适的方法. 通过 JavaScript engine, 你不需任何特殊事情 – 会基于参数类型而选择正确的重载 Java 方法变体. 但是, 有时你可能要(或者不得不)去明确选择一个特殊重载方法变体.

var out = java.lang.System.out;

// select a particular println function
out["println(java.lang.Object)"]("hello");

More details on JavaScript’s Java method overload resolution is at http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html

Implementing Your Own Script Engine

我们不会对兼容 JSR-223 的脚本引擎的实现都面面俱到. 最低限度, 你需要实现 javax.script.ScriptEnginejavax.script.ScriptEngineFactory 接口. 抽象类 javax.script.AbstractScriptEngine 为 ScriptEngine 接口的一些方法提供了有用的默认实现.

在开始实现一个 JSR-223 引擎之前, 你可能要先查看一下 http://scripting.dev.java.net 项目. 该项目为许多流行开源脚本语言维护着 JSR-223 的实现.

References

By javafuns on February 23, 2009 at 09:57 · Views: 1,477 · Permalink
Categorized in: Java · Tagged with: ,
1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 1.00 out of 5)
Loading ... Loading ...

Leave a Reply


  • Highest Rated

  • My PicasaPhotos

    IMG_0874.JPG

    1557e2d1543651c0562c84c3.jpg

    facebook7.JPG

  • RSS My del.icio.us

  • My RSS