一、简介

Rhino是一种用Java编写的开源JavaScript解释器,目的是用Java API实现轻松编写JavaScript程序。Rhino能自动完成Java原生类型与JavaScript原生类型之间的相互转换。

二、安装与运行

java -jar rhino-1.7.7.2.jar

运行Rhino

三、语法

  • Rhino定义了少量的全局函数:
//全局输出函数,将内容输出到控制台
print(x);

//使用JS 1.7语言特性
version(170);

//加载并执行1个或多个JS文件
load(filename, ...);

//读取文本文件内容,并以字符串形式返回
readFile(file);

//读取URL地址的内容,并以字符串形式返回
readUrl(url);

//使用命令行参数运行系统命令
runCommand(cmd, [args...]);

//退出Rhino
quit();

rhino-test-00

  • Rhino可以将Java包和类表示成JS对象:
//全局变量Packages是Java包层次结构的根
Packages.any.package.name

//Packages.java可以简写为java
java.lang

//Packages.javax可以简写为javax
javax.swing

//Rhino把Java包的类表示为JS对象,可以通过赋值给变量获得对应的短名
var System = java.lang.System;
var JFrame = javax.swing.JFrame;

var HashMap = java.util.HashMap;
//或者用更正式的方式导入
importClass(java.util.HashMap);

//可以使用importPackage()导入包。注:不要导入java.lang,因为有太多的名称和JS全局变量冲突
importPackage(java.util);

//可以使用JavaImporter引入多个包中的类,并在with语句中使用它返回的对象
var pkgs = JavaImporter(Packages.java.util, java.swing);//Packages.java中的Packages可省略
with(pkgs){
	//在此处可以使用List、JFrame等类
}

rhino-test-01

  • new和instanceof关键字:
//可以使用new关键字实例化Java类
var list = new java.util.ArrayList();

//Rhino会执行隐式转换,下面的例子中会将'file.txt'自动转为java.lang.String类型
var file = new java.io.File('file.txt');
//调用Java对象file的length方法
var len = file.length();

//rhino能让JS的instanceof运算符用于Java对象
print(list instanceof java.util.List);

rhino-test-02

  • Rhino也允许JS查询或设置Java类的静态字段和Java对象的实例字段。当Java对象存在setter/getter方法时,Rhino会将其显示为Js对象的属性。
var File = java.io.File;
var f = new File('file.txt');
print(f.name);//调用java.io.File.getName()方法

rhino-test-03

  • 重载函数调用:一般情况下,Rhino可以根据调用方法时传递的参数类型判断出要调用的方法,有时也需要通过名字和签名来明确识别方法。
说明:按官方文档,加载自定义扩展类时只要保证.jar.class文件在classpath路径下即可,但我在做测试时这种方式并不可行,需要在命令行下运行以下命令才可以(rhino-test.jar为测试使用类所在jar文件):

java -classpath .\rhino-1.7.7.2.jar;.\rhino-test.jar org.mozilla.javascript.tools.shell.Main

package com.model;

/**
 * 测试账户类
 */
public class Account {

	private float money;

	public void saveMoney(int amount){
		money += amount;
		System.out.println("调用int类型参数的方法");
	}
	
	public void saveMoney(float amount){
		money += amount;
		System.out.println("调用float类型参数的方法");
	}

	public float getMoney() {
		return money;
	}

	public void setMoney(float money) {
		this.money = money;
	}
	
}
//rhino脚本

//引入用户自定义包时Packages不能省略
var account = new Packages.com.model.Account();
//调用int参数方法
account['saveMoney(int)'](50);
//调用float参数方法
account['saveMoney(float)'](55.55);

rhino-test-04

  • 可以使用for/in循环遍历Java类和对象的方法和属性:
importClass(java.lang.System);
importPackage(Packages.com.model);
//输出java.lang.System类的静态成员
for(var f in System){
	print(f);
}
//输出com.model.Account类的实例成员
var account = new Account();
for(var p in account){
	print(p);
}

rhino-test-05

  • Rhino允许像JS数组那样获取、设置Java数组元素:
//必须使用java.lang.reflect.Array来实现数组的创建
var words = java.lang.reflect.Array.newInstance(java.lang.String, 10);

var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'];
//创建完成后,就可以像JS数组一样使用它们
for(var i = 0; i < words.length; i++){
	words[i] = arr[i];
}

rhino-test-06

  • 实现接口或扩展抽象类
//接口
package com.api;

import java.util.List;

import com.model.Account;

public interface IAccountService {

	public Account queryById(int id);
	
	public List<Account> queryByName(String name);
}

//抽象类
package com.api;

import com.model.Account;

public abstract class AbstractAccountHelper {

	public abstract void analysis(Account account);
}

//实现接口
importPackage(Packages.com.api);
var service = new IAccountService({
	queryById: function(id){
		print('query by id: ' + id);
	},
	queryByName: function(name){
		print('query by name: ' + name);
	}
});

//扩展抽象类
var service = new AbstractAccountHelper({
	analysis: function(account){
		print('analysis account');
	}
});

//如果一个对象需要实现多重接口,需要使用JavaAdapter
var obj = new JavaAdapter(java.lang.Runnable, AbstractAccountHelper, {
	run: function(){},
	analysis: function(){}
});

//当接口或抽象类的方法只有一个时,可以直接使用一个函数即可
button.addActionListener(function(e){
	print('button clicked');
});

rhino-test-07

  • 当Java方法抛出异常时,Rhino将其作为JS异常传递,可以通过JS的Error对象的javaException属性获取原始的Java Exception:
try{
	java.lang.System.getProperty(null);
}catch(e){
	print(e.javaException);
}

rhino-test-08

  • Rhino会按需要自动转换原始数字、布尔值和null。因为JavaScript没有char类型,因此Java的char类型会被当作JavaScript数字对待。 JavaScript字符串能自动转换为Java字符串,但java.lang.String对象不能转换为JavaScript字符串。
var version = java.lang.System.getProperty('java.version');
typeof version // => object
version.length() // => 8

//为了将Java字符串转换为JavaScript字符串,需要使用JavaScript的String()方法:
var version = String(java.lang.System.getProperty('java.version'));
  • Java代码中执行Javascript代码
package com.rhino;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;

public class RunScript {

	public static void main(String[] args) {
		Context ctx = Context.enter();
		try{
			Scriptable scope = ctx.initStandardObjects();
			String source = "var d = new Date(); d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate();";
			Object result = ctx.evaluateString(scope, source, "<cmd>", 1, null);
			System.out.println(Context.toString(result));
		}finally{
			Context.exit();
		}
	}
}

运行结果:2018-1-4

参考资料