动态脚本有诸多用途,对于不停机变更执行逻辑,实现部分开放式的业务动态性非常有帮助。不过好的设计需要考虑性能、边界、变更控制等问题,以达到“受控的动态”能力。

JVM提供了不错的脚本接口,从invokedynamicGraalVM,多语言、动态语言、高性能是发展方向。

Groovy作为可以和Java无缝交互的动态语言,利用了invokedynamic来实现核心功能,开发比较活跃,是动态化比较好的选择。下面介绍几种Groovy嵌入Java应用的方法。为达到class缓存、线程安全等目的,一些其他的方法不做介绍。

1、直接使用GroovyScriptEngine,加载test1.groovy文件执行:

public void testRun1() {
  String[] roots = new String[] { getCwd() + "\\src\\test\\groovy\\"};
  try {
    GroovyScriptEngine gse = new GroovyScriptEngine(roots);
    Binding binding = new Binding();
    binding.setVariable("input", "world");
    gse.run("test1.groovy", binding);
    System.out.println(binding.getVariable("output"));
  } catch (Exception e) {
    e.printStackTrace();
  }
}

test1.groovy如下:

output = "Hello, ${input}!"

问题:如何做性能加速?

2、使用GroovyClassLoader,加载test2.groovy文件执行:

public void testRun2() {
  ClassLoader parent = getClass().getClassLoader();
  GroovyClassLoader loader = new GroovyClassLoader(parent);

  try {
    String filePath = getCwd() + "\\src\\test\\groovy\\test2.groovy";
    Class groovyClass = loader.parseClass(new File(filePath));
    Binding binding = new Binding();
    binding.setVariable("input", "world");
    GroovyObject groovyObject = (GroovyObject) groovyClass.getDeclaredConstructor().newInstance();
    Object[] args = {binding};
    groovyObject.invokeMethod("run", args);
    System.out.println(binding.getVariable("output"));
  } catch (Exception e) {
    e.printStackTrace();
  }
}

test2.groovy如下:

def run(binding) {
  println binding.getVariable("input");
  binding.setVariable("output", "hi test2")
}

问题:如何做性能加速?

3、使用ScriptEngineManager获取脚本引擎,加载test3.groovy文件执行:

public void testRun3() {
  try {
    String scriptFile = getCwd() + "\\src\\test\\groovy\\test3.groovy";
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("groovy");
    SimpleBindings binding = new SimpleBindings();
    binding.put("input", "groovy ScriptEngineManager");
    if (engine instanceof Compilable) {
      Compilable compilable = (Compilable) engine;
      CompiledScript compiledScript = compilable.compile(new FileReader(new File(scriptFile)));
      Object out = compiledScript.eval(binding);
      System.out.println("eval output is: " + out.toString());
    }
  } catch (Exception e) {
    e.printStackTrace();
  }
}

test3.groovy如下:

output = "Hello, ${input}!";
return output;

问题:如何做性能加速?

对于jdk8~11使用ScriptEngineManager还可以获取javascript脚本引擎,和testRun3对groovy脚本操作类似。加载test4.js文件执行如下:

public void testRun4() {
  try {
    String scriptFile = getCwd() + "\\src\\test\\js\\test4.js";
    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName("javascript");
    SimpleBindings binding = new SimpleBindings();
    binding.put("input", "javascript ScriptEngineManager");
    if (engine instanceof Compilable) {
      Compilable compilable = (Compilable) engine;
      CompiledScript compiledScript = compilable.compile(new FileReader(new File(scriptFile)));
      Object out = compiledScript.eval(binding);
      System.out.println("eval output is: " + out.toString());
    }
  } catch (Exception e) {
    e.printStackTrace();
  }
}

test4.js如下:

output = "Hello, " + input;
output;