6.3 节我们看到了如何在 AviatorScript 中调用 Java 函数,这里我们将介绍如何用 Java 提供的脚本 API 来调用 AviatorScript 脚本中的函数等。

AviatorScript 内置了对 Java Scripting API 的支持,并且提供了 AviatorScriptEngineFactory 的 SPI 实现,只要你的 classpath 包含了 aviator 的 jar 引用,就可以直接使用。我们来看一些例子(所有示例代码在源码 example 的 scripting 目录)。

获取执行引擎

通过 ScriptEngineManager 可以获得 AviatorScript 的执行引擎:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.ScriptEngine;
  3. import javax.script.ScriptEngineManager;
  4. public class ScriptEngineExample {
  5. public static void main(final String[] args) {
  6. final ScriptEngineManager sem = new ScriptEngineManager();
  7. ScriptEngine engine = sem.getEngineByName("AviatorScript");
  8. }
  9. }

接下来我们将使用这个 engine 做各种例子演示。

配置执行引擎

可以从 ScriptEngine 里获取底层的 AviatorEvaluatorInstance 引用,进行引擎的相关配置:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.ScriptEngine;
  3. import javax.script.ScriptEngineManager;
  4. import com.googlecode.aviator.AviatorEvaluatorInstance;
  5. import com.googlecode.aviator.Feature;
  6. import com.googlecode.aviator.Options;
  7. import com.googlecode.aviator.script.AviatorScriptEngine;
  8. public class ConfigureEngine {
  9. public static void main(final String[] args) throws Exception {
  10. final ScriptEngineManager sem = new ScriptEngineManager();
  11. ScriptEngine engine = sem.getEngineByName("AviatorScript");
  12. AviatorEvaluatorInstance instance = ((AviatorScriptEngine) engine).getEngine();
  13. // Use compatible feature set
  14. instance.setOption(Options.FEATURE_SET, Feature.getCompatibleFeatures());
  15. // Doesn't support if in compatible feature set mode.
  16. engine.eval("if(true) { println('support if'); }");
  17. }
  18. }

默认的引擎处于下列模式:

  1. 全语法特性支持
  2. 缓存编译模式
  3. 启用基于反射的 java 方法调用

求值

最简单的,你可以直接执行一段 AviatorScript 脚本,调用 eval(script) 方法即可

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.ScriptEngine;
  3. import javax.script.ScriptEngineManager;
  4. public class EvalScriptExample {
  5. public static void main(final String[] args) throws Exception {
  6. final ScriptEngineManager sem = new ScriptEngineManager();
  7. ScriptEngine engine = sem.getEngineByName("AviatorScript");
  8. engine.eval("print('Hello, World')");
  9. }
  10. }

这将打印 Hello, World 到控制台,调用了 print 函数,

如果你的脚本是文件,也可以用 eval(reader) 方法:

  1. import javax.script.*;
  2. public class EvalFile {
  3. public static void main(String[] args) throws Exception {
  4. // create a script engine manager
  5. ScriptEngineManager factory = new ScriptEngineManager();
  6. // create AviatorScript engine
  7. ScriptEngine engine = factory.getEngineByName("AviatorScript");
  8. // evaluate AviatorScript code from given file - specified by first argument
  9. engine.eval(new java.io.FileReader(args[0]));
  10. }
  11. }

文件名通过执行的第一个参数指定。

默认引擎处于缓存表达式模式。

注入变量

可以注入全局变量到脚本,并执行:

  1. package com.googlecode.aviator.example.scripting;
  2. import java.io.File;
  3. import javax.script.ScriptEngine;
  4. import javax.script.ScriptEngineManager;
  5. public class ScriptVars {
  6. public static void main(final String[] args) throws Exception {
  7. ScriptEngineManager manager = new ScriptEngineManager();
  8. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  9. File f = new File("test.txt");
  10. // expose File object as variable to script
  11. engine.put("file", f);
  12. // evaluate a script string. The script accesses "file"
  13. // variable and calls method on it
  14. engine.eval("print(getAbsolutePath(file))");
  15. }
  16. }

这里我们将文件 f 通过 engine.put 方法作为全局变量注入,然后执行脚本 print(getAbsolutePath(file)) ,打印文件的绝对路径。

默认引擎启用了基于 java 反射的方法调用模式。

编译脚本并执行

AviatorScript 也支持了 Scripting API 的预编译模式:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.Bindings;
  3. import javax.script.Compilable;
  4. import javax.script.CompiledScript;
  5. import javax.script.ScriptEngine;
  6. import javax.script.ScriptEngineManager;
  7. public class CompileScript {
  8. public static void main(final String[] args) throws Exception {
  9. ScriptEngineManager manager = new ScriptEngineManager();
  10. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  11. Compilable compilable = (Compilable) engine;
  12. CompiledScript script = compilable.compile("a + b");
  13. final Bindings bindings = engine.createBindings();
  14. bindings.put("a", 99);
  15. bindings.put("b", 1);
  16. System.out.println(script.eval(bindings));
  17. }
  18. }

我们将表达式 a+b 编译成一个 CompiledScript 对象,接下来通过 createBindings 创建了一个环境绑定,将 a 和 b 分别绑定为 99 和 1,然后执行 eval(bindings) ,结果为 100。

默认编译也是启用缓存表达式模式。

调用脚本函数

在 java 中调用 script 函数也同样支持:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.Invocable;
  3. import javax.script.ScriptEngine;
  4. import javax.script.ScriptEngineManager;
  5. public class InvokeScriptFunction {
  6. public static void main(final String[] args) throws Exception {
  7. ScriptEngineManager manager = new ScriptEngineManager();
  8. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  9. // AviatorScript code in a String
  10. String script = "fn hello(name) { print('Hello, ' + name); }";
  11. // evaluate script
  12. engine.eval(script);
  13. // javax.script.Invocable is an optional interface.
  14. // Check whether your script engine implements or not!
  15. // Note that the AviatorScript engine implements Invocable interface.
  16. Invocable inv = (Invocable) engine;
  17. // invoke the global function named "hello"
  18. inv.invokeFunction("hello", "Scripting!!" );
  19. }
  20. }

我们在脚本里定义了 hello 函数,然后通过 Invocable 接口就可以在 java 代码里调用并传入参数:

  1. Hello, Scripting!!

在 AviatorScript 中可以使用 map 和闭包来模拟面向对象编程,同样,我们可以在 java 代码里调用 AviatorScript 中“对象”的方法:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.Invocable;
  3. import javax.script.ScriptEngine;
  4. import javax.script.ScriptEngineManager;
  5. public class InvokeScriptMethod {
  6. public static void main(final String[] args) throws Exception {
  7. ScriptEngineManager manager = new ScriptEngineManager();
  8. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  9. // AviatorScript code in a String. This code defines a script object 'obj'
  10. // with one method called 'hello'.
  11. String script =
  12. "let obj = seq.map(); obj.hello = lambda(name) -> print('Hello, ' + name); end;";
  13. // evaluate script
  14. engine.eval(script);
  15. // javax.script.Invocable is an optional interface.
  16. // Check whether your script engine implements or not!
  17. // Note that the AviatorScript engine implements Invocable interface.
  18. Invocable inv = (Invocable) engine;
  19. // get script object on which we want to call the method
  20. Object obj = engine.get("obj");
  21. // invoke the method named "hello" on the script object "obj"
  22. inv.invokeMethod(obj, "hello", "Script Method !!");
  23. }
  24. }

我们定义了对象 obj ,它有一个方法 hello(name) ,在 java 代码里通过 engine.get("obj") 获取该对象,然后通过 Invocable 接口调用 invokeMethod(obj, 方法名,方法参数列表) 就可以调用到该对象的方法。

使用脚本实现 Java 接口

我们可以用 AviatorScript 脚本实现 java 中的接口,然后将函数或者对象方法转成该接口在 java 代码里使用,比如我们用 AviatorScript 实现 Runnable 接口:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.Invocable;
  3. import javax.script.ScriptEngine;
  4. import javax.script.ScriptEngineManager;
  5. public class RunnableImpl {
  6. public static void main(final String[] args) throws Exception {
  7. ScriptEngineManager manager = new ScriptEngineManager();
  8. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  9. // AviatorScript code in a String
  10. String script = "fn run() { println('run called'); }";
  11. // evaluate script
  12. engine.eval(script);
  13. Invocable inv = (Invocable) engine;
  14. // get Runnable interface object from engine. This interface methods
  15. // are implemented by script functions with the matching name.
  16. Runnable r = inv.getInterface(Runnable.class);
  17. // start a new thread that runs the script implemented
  18. // runnable interface
  19. Thread th = new Thread(r);
  20. th.start();
  21. }
  22. }

我们在 AviatorScript 实现了一个 run() 函数, 接下来就可以从引擎里获取一个 Runnable 接口实现,它会自动去调用已定义的 run 函数并执行,然后我们将获取的 Runnable 实例用在了线程里:

  1. run called

不仅是函数,对于“对象”也同样可以的:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.Invocable;
  3. import javax.script.ScriptEngine;
  4. import javax.script.ScriptEngineManager;
  5. public class RunnableImplObject {
  6. public static void main(final String[] args) throws Exception {
  7. ScriptEngineManager manager = new ScriptEngineManager();
  8. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  9. // AviatorScript code in a String
  10. String script =
  11. "let obj = seq.map(); obj.run = lambda() -> println('run method called'); end; ";
  12. // evaluate script
  13. engine.eval(script);
  14. // get script object on which we want to implement the interface with
  15. Object obj = engine.get("obj");
  16. Invocable inv = (Invocable) engine;
  17. // get Runnable interface object from engine. This interface methods
  18. // are implemented by script methods of object 'obj'
  19. Runnable r = inv.getInterface(obj, Runnable.class);
  20. // start a new thread that runs the script implemented
  21. // runnable interface
  22. Thread th = new Thread(r);
  23. th.start();
  24. }
  25. }

你可以将某个对象的方法转成特定接口的实现。

多 Scope 支持

在上面的注入变量一节,我们注入了全局变量,Scripting API 也支持多个全局变量环境同时执行,相互隔离,这是通过 ScriptContext 实现,你可以把他理解成一个类似 Map<String, Object> 的映射:

  1. package com.googlecode.aviator.example.scripting;
  2. import javax.script.Bindings;
  3. import javax.script.ScriptContext;
  4. import javax.script.ScriptEngine;
  5. import javax.script.ScriptEngineManager;
  6. import javax.script.SimpleScriptContext;
  7. public class MultiScopes {
  8. public static void main(final String[] args) throws Exception {
  9. ScriptEngineManager manager = new ScriptEngineManager();
  10. ScriptEngine engine = manager.getEngineByName("AviatorScript");
  11. engine.put("x", "hello");
  12. // print global variable "x"
  13. engine.eval("println(x);");
  14. // the above line prints "hello"
  15. // Now, pass a different script context
  16. ScriptContext newContext = new SimpleScriptContext();
  17. Bindings engineScope = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
  18. // add new variable "x" to the new engineScope
  19. engineScope.put("x", "world");
  20. // execute the same script - but this time pass a different script context
  21. engine.eval("println(x);", newContext);
  22. // the above line prints "world"
  23. }
  24. }

newContext 的 engine 级别绑定里我们重新定义了 x 为 world 字符串,并传入 eval 执行,两者打印的结果将不同:

  1. hello
  2. world