Java平台使用脚本语言
java平台可以使用的脚本语言
- js
- groovy
- Renjin
- sisc
使用java提供的脚本引擎对脚本语言进行解析
java提供了一个脚本引擎:ScriptEngine,我们需要通过这个脚本引擎来对我们的脚本语言进行解析。
获取ScriptEngine
构造一个ScriptEngineManager,并调用getEngineByxx方法来获取对应的ScriptEngine
//构建一个脚本引擎管理器
ScriptEngineManager manager = new ScriptEngineManager();
//获取js的解析引擎
ScriptEngine jsEngine = manager.getEngineByName("js");
获取当前java所支持的引擎名,MIME类型和文件扩展名
可使用ScriptEngineManager 的getEngineFactories()方法来获取。
ScriptEngineManager manager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = manager.getEngineFactories();
jdk1.8,当前内置只支持一个脚本引擎:Nashorn;
这是有Oracle开发的一个js解释器,如果想用使用其他脚本语言的解释器,可以通过在类路径中提供必要的jar包来添加对更多语言的支持。----《java核心技术卷二》 p353
使用js脚本引擎解析js脚本
简单运行
以下代码仅仅对于js脚本解析器的简单使用,通过jsEngine.eval();方法传入一段脚本,或者一个js文件输入流;脚本的内容也很简单就是将var a = 10;然后通过jsEngine.get(“key”),方法从脚本中读取一个var变量进行输出;但这远远不是js脚本引擎的所有功能。
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");
//直接使用string 模拟脚本语言进行测试
String jsScript = "var a = 10";
jsEngine.eval(jsScript);
int strA = (int)jsEngine.get("a");
System.out.println(strA);
//使用js脚本文件进行测试
File file = new File("test.js");
if (!file.exists()) {
return;
}
Reader reader = new FileReader(file);
jsEngine.eval(reader);
int fileA = (int)jsEngine.get("a");
System.out.println(fileA);
}
ScriptEngine中的方法
-
eval(“script”):执行传入的脚本,如果该脚本有返回值,就返回该值;
-
get(“key”):从脚本获取key对应的value
-
put"(“key”,value):在引擎作用域内传入一个key = value的键值对
-
createBindings():创建一个适合该引擎的空Bindings对象
-
getBindings():返回绑定的Bindings对象
-
setBindings():
-
getContext()
-
setContext()
-
getFactory()
引擎作用域和全局作用域
引擎作用域:由ScriptEngine使用eval或者put方法,在脚本中构建的key:value键值对;
全局作用域:有ScriptEngineManage使用put方法,在脚本中够构建的key:value键值对;
其他作用域:实现ScriptContext接口的类,这些类可以作为自定义的作用域,由一个整数标识,越小的数字应该越先被搜索到。
Bindings对象
key:value:可以称为一个变量绑定;而Bindings对象可以存放多个key:value这样的绑定,而且可以在同一个作用域中的其他绑定的变量;
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");
Bindings jsBindings = jsEngine.createBindings();
jsBindings.put("a",10);
jsEngine.eval("var b = a",jsBindings);
Object b = jsBindings.get("b");
System.out.println(b);
Object b2 = jsBindings.get("b");//10,说明 一个变量绑定是可以多次使用的
System.out.println(b2);
Object b4 = jsEngine.get("b");
System.out.println(b4);//null, 说明在使用eval方法时传入一个Binding对象,传入脚本的变量绑定也会放在这个Binding对象中
jsEngine.eval("var e = 30");
Object e = jsEngine.get("e");
System.out.println("e" + e);//30,说明如果没有传入Binding对象,则会放到jsEngine的公共域中
Object b1 = manager.get("b");
System.out.println(b1);//null 说明 b 变量 是属于 jsEngine的
manager.put("c",20);
Object c = jsEngine.get("c");
System.out.println(c);// 20 说明 c 变量 是属于公公域的
}
重定向输入和输出
可以通过脚本上下文(ScriptEngine)的setReader和SetWriter方法来重定向脚本的标准输入和输出
如下数据,world就会被重定向到控制台输出
print("Hello");
java.lang.System.out.println("World");
代码如下
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");
StringWriter writer = new StringWriter();//设置一个输出流
jsEngine.getContext().setWriter(new PrintWriter(writer,true));//修改数据输出方式
Reader reader = new FileReader(new File("redirct.js"));
jsEngine.eval(reader);
}
由于Nashorn引擎没有标准的输入源的概念,因此调用setReader没有效果。
除了可以设置脚本正常运行的输出,也可以设置脚本运行出错的输出:
setErrorWriter(Writer writer);
调用脚本的函数和方法
使用ScriptEngine调用javaScript或者其他脚本语言的方式有以下几种。
- 使用脚本中的函数的函数名来调用: InvokeFunction()
- 如果脚本语言是面向对象的,就可以使用: InvokeMethod()
- 让脚本引擎去实现一个接口,通过这个接口的实例对象去调用脚本中的方法
InvokeFunction
使用该方法调用脚本的函数需要注意的是,对应的脚本引擎必须实现了Invokeable接口,即可以使用java的代理机制。具体内容在《java核心技术卷一第6章》p258
首先要在脚本文件中或者再脚本字符串中有一个方法;有无返回值并没有关系;例如我这边使用的是外置的js文件。
function greet(how, whom) {
return how + ',' + whom + '!';
}
然后使用ScriptEngine的eval方法将这个脚本文件传到这个引擎作用域中。
Reader reader = new FileReader(new File("jsFunction.js"));
jsEngine.eval(reader);//将脚本传入js解析器
//调用该脚本中的方法
Object result = ((Invocable) jsEngine).invokeFunction("greet", "hello", "world");
System.out.println(result);
//输出:hello,world!
小结:使用invokeFunction使用java代码区调用js函数,首先需要将当前的ScriptEngine强转为Invocable接口类型,然后需要知道这个js函数的函数名,并且需要传入这个js函数需要的参数。这样就可以使用这个脚本中的函数了。
InvokeMethod
如果脚本语言是面向对象的脚本语言,例如js;则可以使用InvokeMethod方法去调用当前脚本的函数。
function Greet(how) {
this.how = how;
}
Greet.prototype.welcome= function (whom) {
return this.how + ',' + whom + '!';
}
上面的代码其实就是在js中申明了一个方法Greet,并且用这个方法的prototype对象又添加了一个方法welcome;
//将上述js文件传入当前引擎作用域
sEngine.eval(reader);
//实例化一个Greet方法的对象
Object yo = jsEngine.eval("new Greet('Yo')");
//传入当前Greet方法的对象,以及其内部方法welcome的方法名和参数
Object result = ((Invocable) jsEngine).invokeMethod(yo,"welcome","World");
//输出返回值
System.out.println(result);
//Yo,World
小结:必须为面向对象的脚本语言;
让脚本引擎去实现一个java接口,使用这个接口的java方法去调用脚本中的函数。
脚本中的方法名和参数必须和java接口中的方法名和参数一致。
js文件
function welcome(whom) {
return 'hello' + whom + '!'
}
java代码:
定义的接口
public interface Greeter {
String welcome(String whom);
}
实现
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine js = manager.getEngineByName("js");
js.eval(new FileReader(new File("jsFunctionInterface")));
//让当前脚本引擎实现Greeter接口,并返回这个接口的一个实例
Greeter g = ((Invocable) js).getInterface(Greeter.class);
//使用这个接口中的welcom方法去调用脚本中的函数
String world = g.welcome("world");
System.out.println(world);
//helloworld!
在面向对象的脚本语言中,可以通过相匹配的java接口来访问一个脚本类;
//这里用到js代码是InvokeMethod中的js代码
jsEngine.eval(reader);
Object yo = jsEngine.eval("new Greet('Yo')");
Greeter g = ((Invocable) jsEngine).getInterface(yo, Greeter.class);
String world = g.welcome("World");
System.out.println(world);
编译脚本,提高脚本在jav中的执行效率
如果当前的脚本引擎实现了Compilable接口,就可以将这些脚本代码编译为某种中间格式,提高执行效率。
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");
Reader reader = new FileReader(new File("test.js"));
CompiledScript script = null;
if (jsEngine instanceof Compilable) {
script = ((Compilable)jsEngine).compile(reader);
}
if (script != null) {
Object eval = script.eval();
System.out.println(eval);
} else {
Object eval = jsEngine.eval(reader);
System.out.println("uncompilable" + eval);
}
当前不是很清楚将脚本编译后该怎么调用脚本中的函数。。。。