2024年1月2日发(作者:)

Rhino脚本引擎技术介绍

Rhino是一个开源的脚本引擎框架,可以运行类似javascript语法的脚本,并可以调用java的方法,并可以嵌入Java执行,脚本修改后不需要重启JVM进程,就可以使用解析或编译方式执行,非常方便。由于Rhino脚本中可以写入任何表达式和javacript程序,既可以进行条件规则的判断,也可以进行各类简单或复杂的计算,因此是BPS中以前参与者规则和连线规则的一个良好替代方案,在某银行新一代流程平台中,我们使用了Rhino脚本引擎来替代以前基于Antlr词法分析器的规则引擎。

由于Rhino的灵活和强大的功能,从JDK1.6开始,JDK将Rhino开源软件纳入JDK内部API中,形成了以为包名的脚本引擎API。

使用Rhino有如下好处:

1、实现简单,灵活,功能强大,对比以前BPS规则用的Antlr词法分析器,实现更加简单,不需要进行规则文件编辑和代码生成(而且对不同规则需要生成多套代码,很不灵活),脚本引擎可以进行几乎任何运算或Java调用,能满足某银行新一代流程平台的要求。

2、即时生效,修改脚本后不用重启Java进程就可以立即生效运行。

3、有编译和解析两种运行方式,编译方式在大量并发的调用情况下性能更好。

4、轻量,JDK内置,不需要引入其他第三方jar。

下面主要介绍JDK内置的Rhino脚本引擎,以及其的API用法。

翻开JDK1.6的的API,可以看到脚本API中只包含6个接口和6个类(其中一个还是一个异常),整个API非常简单易用。

主要的类图如下(类图做了一些省略,对一些不常用的类和接口省略了,并只显示了主要的方法):

<>Bindings+get(Object key): Object+put(String name, Object value)useCompiledScript+eval(): Object+eval(Bindings): Objectcompile<>Compilable+compile(String script): CompiledScriptSimpleBindings<>ScriptEngine+eval(String script): Object+eval(String script, Bindings n): Object+createBindings(): Bindings+getBindings(int scope): Bindings+setBindings(Bindings bindings, int scope)getScriptEngineManager+getEngineByName(String shortName): ScriptEngineRhinoScriptEngineAbstractScriptEngine

Bindings接口可以理解为上下文,可以往上下文中设置一个Java对象或通过key获取一个对象,它有一个实现类,SimpleBindings,内部就是一个map。

上下文是给脚本引擎执行脚本时使用的,脚本引擎在执行脚本的时候,用到上下文中放置的Java对象,执行其方法,使用其属性。

ScriptEngine接口就是脚本引擎,用于执行脚本计算结果的接口,其实现类是AbstractScriptEngine和底层的RhinoScriptEngine,这些类是脚本引擎的核心类。eval(String script)和eval(String script, Bindings n)两个方法就是执行一段脚本返回计算结果的两个方法,第二个方法会传入上下文,即运行脚本时,脚本需要使用上下文中设置的Java变量的方法或属性。

上下文是有范围的,分三种范围:

1) 全局的:所有脚本引擎都可以使用的,由ScriptContext. GLOBAL_SCOPE常量来定义其范围的名称。dings(_SCOPE)可以获GLOBAL取这类上下文。

2) 引擎即的:只是一个脚本引擎可以使用的,由ScriptContext. ENGINE_SCOPE常量来定义其范围的名称。dings(_SCOPE)可以获取这类上下文。

3) 局部的:引擎的一次计算用到的Bindings,没有常量定义,Bindings()创建的就是这类上下文。

ScriptEngineManager可以认为是脚本引擎的一个管理类或工厂,一般我们获取javascript脚本引擎时,会调用这个类实例的getEngineByName(“js”)方法来获得支持js语法的脚本引擎。

脚本引擎在执行脚本的时候有两种方式:

1) 解释执行:直接运行ScriptEngine的eval方法

2) 编译执行:运行CompiledScript的eval方法,脚本引擎的内部实现类RhinoScriptEngine又实现了Compilable接口,可以将脚本语句编译成编译后脚本(CompiledScript),CompiledScript可以直接运行eval方法,进行编译方式的脚本执行。

按理来说,编译执行要快于解释执行,但在实际的测试中发现,只有在大量计算的情况下(循环次数在1-10万以上),编译执行快于解释执行,否则,解释执行可能更快。

下面举例说明脚本引擎的基本用法:

1、 进行简单的脚本表达式计算:

package engine;

import Engine;

import EngineManager;

import Exception;

public class SimpleScript {

public static void main(String[] args) throws ScriptException {

ScriptEngineManager seManager=new ScriptEngineManager();

ScriptEngine se=ineByName("js");

Object ret=("3+4;");

n(ret);

}

}

计算结果打印出7.0.

2、 使用Bindings上下文计算

package engine;

import gs;

import Engine;

import EngineManager;

import Exception;

public class ScriptUsingBindings {

public static void main(String[] args) throws ScriptException {

ScriptEngineManager seManager=new ScriptEngineManager();

ScriptEngine se=ineByName("js");

Bindings bindings=Bindings();

("user", new User("张三",19));

Object ret=("print(e()); if(>=18) '已成年'; else '未成年';",bindings);

n(ret);

}

}

User是一个对象,用name和age两个属性和getName()和getAge()两个方法,在脚本中使用和()都可以。

计算后结果返回:张三已成年

以上两个例子都是使用解释方式进行计算,下面的例子使用编译的方式进行计算。

3、 使用编译方式进行计算。

package engine;

import gs;

import able;

import edScript;

import Engine;

import EngineManager;

import Exception;

public class CompiledUsingBindings {

public static void main(String[] args) throws ScriptException {

ScriptEngineManager seManager=new ScriptEngineManager();

ScriptEngine se=ineByName("js");

Compilable ce=(Compilable)se;

String script="println(e()+'的年龄为'+());if(>=18) '已成年'; else '未成年';";

CompiledScript cs=e(script);

}

}

Bindings bindings=Bindings();

("user", new User("张三",19));

Object ret=(bindings);

n(ret);

控制台打印出:

张三的年龄为19

已成年

注意:print,println是脚本引擎内置函数,是向控制台打印输出。

4、 解释方式执行javascript函数

package engine;

import Engine;

import EngineManager;

import Exception;

public class JSFunc {

public static void main(String[] args) throws ScriptException {

ScriptEngineManager seManager=new ScriptEngineManager();

ScriptEngine se=ineByName("js");

String script="function sum(a,b) { return a+b; }";

(script);

Object ret=("sum(3,4)");

n(ret);

}

}

返回结果为7.0

(script);

Object ret=("sum(3,4)");

这种语法和“function sum(a,b) { return a+b; } sum(3,4);”语法相同。

5、 编译方式执行javascript函数

package engine;

import gs;

import able;

import edScript;

import Engine;

import EngineManager;

import Exception;

public class JSFuncCompiled {

public static void main(String[] args) throws ScriptException {

ScriptEngineManager seManager=new ScriptEngineManager();

ScriptEngine se=ineByName("js");

Compilable ce=(Compilable)se;

String script="function sum(a,b) { return a+b; } sum(a,b);";

CompiledScript cs=e(script);

Bindings bindings=Bindings();

("a", 3);

("b", 4);

Object ret=(bindings);

n(ret);

}

}

返回结果为7.0

6、 JS函数调用的高级用法

package engine;

import .*;

public class InvokeScriptFunction {

public static void main(String[] args) throws Exception {

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = ineByName("JavaScript");

// JavaScript code in a String

String script = "function hello(name) { print('Hello, ' + name); }";

// evaluate script

(script);

// ble 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"

Function("hello", "Scripting!!" );

}

}

7、 函数使用高级用法2,调用对象的方法。

package engine;

import .*;

public class InvokeScriptMethod {

public static void main(String[] args) throws Exception {

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = ineByName("JavaScript");

// JavaScript code in a String. This code defines a script object 'obj'

// with one method called 'hello'.

String script = "var obj = new Object(); = function(name)

{ print('Hello, ' + name); }";

// evaluate script

(script);

// ble 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 = ("obj");

// invoke the method named "hello" on the script object "obj"

Method(obj, "hello", "Script Method !!" );

}

}

8、 调用js文件

package va;

import ader;

import Engine;

import EngineManager;

public class ListFileTest {

public static void main(String[] args){

ScriptEngineManager manager = new ScriptEngineManager();

ScriptEngine engine = ineByName("js");

String jsFilename="";

try {

(new FileReader(jsFilename));

}

} catch (Exception e) {

tackTrace();

}

}

内容如下:

importClass();

var rootDir = new File("c:/");

var files = les();

var fixlength=40;

for(var i=0;i<;i++){

var apath=files[i].getAbsolutePath();

print(apath);

for(var j=0;j<();j++)

print(" ");

println((files[i].isDirectory()?"dir":"file"));

};

控制台输出c盘的目录文件列表:

c:$360Section dir

c:$ dir

c:360SANDBOX dir

c:antlr dir

c: file

c:bea dir

c: dir

c:Documents and Settings dir

c: file

c: file

c:HP Universal Print Driver dir

c: file

c: file

c:KRECYCLE dir

c:KRSHistory dir

c:MSOCache dir

... ...

例子就列举这么多,要学习的同学可以下载JDK1.7的JavaDoc,里面有scripting的学习资料。下面,写脚本要注意下面的事项:

1、 脚本可以由返回值,也可以没有返回值。如果要返回值,返回语句一定要写在最后一行。如:

var a=5;

var b=6;

a;

b;

上面语句只会返回b的值6,不会返回a的值5.

2、 返回值不支持这样的语法:“var a=5;”,应该是“var a=5; a;”

3、 每一个语句后建议加“;”,如果一行就是一个语句,可以不加分号,但如果一行写几个语句,每个语句必须使用分号分隔,否则脚本引擎无法区分每条语句,会报javascript语法错。

4、 脚本可调用java对象的方法,如果方法名称为javascript的关键字,则方法无法运行,如下面的语法是运行不了的:

var f=new (“c:/”);

();

delete是Javascript的关键字,所以()是无法运行的,会报javascript语法错。

5、 除非在javascript函数里写return语句,在返回语句中使用return是不支持的,如下面的语句是不支持的。

var a=3*6/2;

return a;

正确的写法是:

var a=3*6/2;

a;

6、 在脚本中可以直接new java对象,但对包名是有限制的,可以使用JDK 内置的java类,如,

等,也可以使用用户自定义的类,但包名需要以com或org开头,不能是任意的字符串为包名的开头,如下面是正确的:

var user=new ();

var cust=new er();

importPackage(,); //导入java包

var user=new User();

var cust=new Customer();

下面是不正确的:

var user=new ();

var cust=new er();

会报错,Exception: ror: ReferenceError: "demo"

is not defined.