用 Rhino 脚本化 Java


Rhino 是一种用 Java 编写的 JavaScript 解释器,其设计目标是借助于强大的 Java 平台 API 实现轻松编写 JavaScript 程序。Rhino 能自动完成 JavaScript 原生类型和 Java 原生类型之间的相互转换,因此 JavaScript 脚本可以设置、查询 Java 属性,并调用 Java 方法。

安装构建 Rhino

git clone https://github.com/mozilla/rhino.git
cd rhino
./gradlew jar

构建完成后即可通过如下命令开启一个 shell 页面:

java -jar buildGradle/libs/rhino-1.7.11-SNAPSHOT.jar

如果要运行指定的 JavaScript 文件,如 program.js,也可以通过下面的运行执行:

java -jar buildGradle/libs/rhino-1.7.9.jar program.js

Rhino 功能特性

Rhino 定义了一些全局函数,这些函数不是 JavaScript 的核心组成部分:

  • print:将内容输出到控制台(可取代 console.log)
  • version:指定 JavaScript 版本
  • load:加载指定 JavaScript 文件
  • readFile:读取文本文件,并以字符串形式返回内容
  • readUrl:读取 URL 内容,并以字符串形式返回内容
  • spawn:运行指定函数
  • runCommand:运行指定系统命令
  • quit:退出 Rhino

Rhino 会将 Java 包和类表示成 JavaScript 对象:

// 全局变量 Packages 是 Java 包层次结构的根
Packages.any.package.name
java.lang    // java 是 Packages.java 的短名
javax.swing  // javax 是 Packages.javax 的短名

var System = java.lang.System;
var JFrame = javax.swing.JFrame;

也可以通过 import 的方式导入:

// 使用 importClass 导入类
importClass(java.util.HashMap); // 等同于 var HashMap = java.util.HashMap

// 使用 importPackage 导入包
importPackage(java.util);

// 通过 JavaImporter 导入任意数量的包和类
var guipkgs = JavaImporter(java.awt, java.awt.event, Packages.javax.swing);
with (guipkgs) {
  // 这里定义 Font、ActionListener 等类
}

Java 和 JavaScript 一样通过 new 实例化类:

var f = new java.io.File("/tmp/test");
var out = new java.io.FileWriter(f);
f instanceof java.io.File; // Rhino 中 instanceof 可用于 Java 对象

注:在上述实例化过程中,Rhino 执行了隐式类型转换,JavaScript 字符串 "/tmp/test" 自动转换为 Java 的 java.utils.String 类型值。

我们可以在 JavaScript 中调用 Java 方法:

out.write("Hello Rhino!");
out.close();
var len = f.length();

Rhino 也允许 JavaScript 代码查询、设置 Java 类的静态字段和 Java 对象的实例字段:

// 获取静态字段
var stdout = java.lang.System.out;
// 获取实例字段
var name = f.name;  // 调用 f.getName()

Java 允许重载方法(名字相同,参数不同),一般,Rhino 能根据传递的参数类型判断出所要调用方法的版本,但偶尔也需要通过名字和标签名来明确要调用的方法。

使用 for/in 循环能遍历 Java 类和对象的方法、属性:

importClass(java.lang.System);
for (var m in System) print(m);

Rhino 允许 JavaScript 程序获取、设置 Java 数组的元素,但是 Java 数组和 JavaScript 数组并不完全一致:Java 数组长度固定、元素类型统一。由于没有现成的 JavaScript 语法可供 Rhino 扩展 JavaScript 创建新的 Java 数组,因此必须通过 java.lang.reflect.Array 来实现:

var bytes = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 10);
for (var i = 0; i < bytes.length; i++) {
    bytes[i] = i;
}

Java 编程经常涉及实现接口,在 Rhino 中实现如下:

// 实现接口
var handler = new java.awt.event.FocusListener({
  focusGained: function (e) {
    print("got focus");
  },
  focusLost: function (e) {
    print("lost focus");
  }
});

// 继承抽象类
var handler = new java.awt.event.WindowAdapter({
  windowClosing: function (e) {
    java.lang.System.exit(0);
  }
});

// 当接口只有一个方法,可以使用匿名函数取而代之
button.addActionListener(function (e) {
  print("button clicked.");
});

// 如果接口或抽象类所有方法都有相同的签名
// 则可以使用一个单独的函数作为接口的实现
// 且 Rhino 将把方法名作为最后一个参数传入
frame.addWindowListener(function (e, name) {
  if (name === "windowClosing")
    java.lang.System.exit(0);
});

// 如果需要一个对象实现多个接口,则使用 JavaAdapter
var o = new JavaAdapter(java.awt.event.ActionListener, java.lang.Runnable, {
  run: function () {},  // 实现 Runnable
  actionPerformed: function () {}  // 实现 ActionListener
});

当 Java 方法抛出异常,Rhino 会将其转换为 JavaScript 异常传递:

try {
    java.lang.System.getProperty(null);
} catch (e) {
    print(e.javaException);
}

最后,Rhino 会按照需要自动转换原始数字、布尔值和null,Java 的 char 类型会被当做 JavaScript 的数字对待,因为 JavaScript 没有字符类型,JavaScript 字符串能自动转换为 Java 字符串,但是 Java 字符串对象不能转换回 JavaScript 字符串,为了把 Java 值转换成字符串,需要通过 String() 函数实现:

var version = String(java.lang.System.getProperty("java.version"));

点赞 取消点赞 收藏 取消收藏

<< 上一篇: E4X:ECMAScript for XML

>> 下一篇: 用 Node.js 实现异步 I/O