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

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;