在项目中有时候我们需要将部分需要经常修改的业务逻辑做成配置化。大家会首先想到各种形式的配置文件。然而,当业务逻辑真的无规律可循之时,我们如何组织配置文件呢?
也许大家会想,如果能在配置文件中编写动态的代码那该多好。这就引出了本片博文的内容,在Java中引入动态脚本,并获取动态脚本的执行结果。
使用Groovy Script动态脚本
Groovy Script以简单,语法贴近原生Java和执行速度快被大家广泛使用。下面讲解下在如何调用Groovy Script。
- 首先引入Groovy的依赖包
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.5.3</version>
</dependency>
- 编写如下Java代码。该示例为最简化的groovy script应用
// 创建Groovy Shell
GroovyShell groovyShell = new GroovyShell();
// 解析动态groovy script。这里的例子为获取a的length。脚本以string形式传入
Script script = groovyShell.parse("a.length()");
// 创建运行环境上下文的变量绑定
Binding binding = new Binding();
// 上下文放入a变量,值为"Hello"
binding.setProperty("a", "Hello");
// 设置绑定
script.setBinding(binding);
// 执行脚本,获取脚本执行结果
Object run = script.run();
// 打印出执行结果,输出为 5
System.out.println(run);
通过这个例子大家不难发现动态脚本的强大。只需要将脚本作为string,读入我们的Java代码当中就可以动态执行了。
使用JavaScript动态脚本
如果大家在项目中使用的是Java 8,建议大家使用JavaScript作为动态脚本。Java 8自带的Nashorn具有很好的性能,使用时也无需引用额外的jar包。
最简单的例子
让nashorn运行起来只需要如下2行代码:
// 获取nashorn脚本执行引擎
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
// 执行脚本, 控制台会打印出 Hello World
nashorn.eval("print('Hello World')");
使用上下文变量
上面例子也太简单了。如果想使用之前groovy script中的例子,指定上下文变量呢?
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
// 同理使用bindings设置上下文变量
Bindings bindings = new SimpleBindings();
bindings.put("a", "Hello");
nashorn.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
Object eval = nashorn.eval("a.length");
System.out.println(eval);
这段代码中setBindings方法中出现了ScriptContext.ENGINE_SCOPE。它的含义在以下代码中得到解释:
/**
* ...
* @param scope The specified scope. Either <code>ScriptContext.ENGINE_SCOPE</code>,
* <code>ScriptContext.GLOBAL_SCOPE</code>, or any other valid value of scope.
* ...
*/
public void setBindings(Bindings bindings, int scope);
scope
参数值有两个:ScriptContext.ENGINE_SCOPE
和ScriptContext.GLOBAL_SCOPE
。接下来我们查看ScriptContext
源代码:
/**
* EngineScope attributes are visible during the lifetime of a single
* <code>ScriptEngine</code> and a set of attributes is maintained for each
* engine.
*/
public static final int ENGINE_SCOPE = 100;
/**
* GlobalScope attributes are visible to all engines created by same ScriptEngineFactory.
*/
public static final int GLOBAL_SCOPE = 200;
这下他们的含义就一目了然了。ENGINE_SCOPE只对当前这个ScriptEngine有效,而GLOBAL_SCOPE对同一个ScriptEngineFactory创建出来的所有engine都生效。
读取外部JavaScript文件
如果JavaScript代码太长了不便于写在程序中怎么办?脚本引擎的eval方法支持从文件系统读取外部js文件。代码如下所示:
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
nashorn.eval(new FileReader("test.js"));
调用JavaScript方法
Nashorn 引擎还支持调用JavaScript中的特定函数。在Java中指定函数名和参数列表,函数执行结果再返回到Java中。
test.js
function fun1() {
return "Hello from fun1";
}
ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn");
// 读取test.js文件
nashorn.eval(new FileReader("test.js"));
// 转换nashorn为Invocable
Invocable invocable = (Invocable) nashorn;
// 显式指明调用fun1,参数列表为空数组
Object returnedValue = invocable.invokeFunction("fun1", new Object[]{});
// 打印调用结果,为 Hello from fun1
System.out.println(returnedValue);
更多资料
其他资料大家可以参考:
https://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html
https://winterbe.com/posts/2014/04/05/java8-nashorn-tutorial/
本博客为作者原创,欢迎大家参与讨论和批评指正。如需转载请注明出处。
网友评论