前两天接到一个需求,大致归纳成以下三点:

  • 要通过Java来解析并运行一段脚本
  • 还不确定是什么语言的脚本
  • 最终效果是Java把数据传进脚本里处理,处理完再接回来

看起来是不是很简单,但这做起来就不太顺手啊。
经过一番了解,在我在哪里可以找到可用的JSR-223脚本语言列表?这个帖子里找到相关的知识。
原来早在JDK1.6开始,Java就引入了JSR-223规范,让我们通过脚本引擎这种统一接口的方式在JVM上运行脚本语言。
所以我最终选择了ScriptEngine来完成这个需求,他本身仅支持JavaScript,经过扩展后可支持PythonRubyGroovy等。

直奔主题,敲代码!

编写逻辑操作类

这个类会被解析成脚本中对应的变量,可在脚本中调用类中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Codec {
// 存放上下文内容,用于回传数据
private final Sinks.Many<Context> processor = Sinks.many().multicast().onBackpressureBuffer(Integer.MAX_VALUE);

// 映射解析方法 - 数据传入脚本
public Codec decoder(Function<Object, Context> function) {
Context context = new Context();
function.apply(context);
return this;
}

// 映射透传方法 - 数据回传
public Codec handler(Context context) {
processor.tryEmitNext(context);
return this;
}

// 监听回传数据流
public Flux<Context> handlePayload() {
return processor.asFlux();
}
}

编写上下文类

这里只有一个方法,用来测试对象传到脚本后,对象内的方法是否可以正常调用

1
2
3
4
5
public class Context {
public String getMessage(){
return "test msg";
}
}

编写js脚本解析测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void jsEngine() throws ScriptException {
String code = "codec.decoder(function (context) {\n" +
" var message = context.getMessage();\n" +
" codec.handler(context);\n" +
" return {\n" +
" messageType:\"REPORT_PROPERTY\"//消息类型\n" +
" };\n" +
"});";

// 获取脚本引擎
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
// 脚本预编译
Compilable compilable = (Compilable) engine;
CompiledScript compiledScript = compilable.compile(code);
ScriptContext scriptContext = new SimpleScriptContext();
// 创建逻辑类,监听回传数据流
Codec codec = new Codec();
codec.handlePayload()
.flatMap(context -> {
System.out.println("message content: " + context.getMessage());
return Mono.empty();
})
.subscribe();
// 把逻辑类放进脚本
scriptContext.setAttribute("codec", codec, ScriptContext.ENGINE_SCOPE);
// 执行脚本
compiledScript.eval(scriptContext);
}

这个时候你就可以看到控制台打印出有message content: test msg的字样,如果你不放心数据有没有经过脚本这一层,大可在脚本里再print一次。

扩展其他语言的脚本

添加对应的Maven依赖

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>com.sun.script.jruby</groupId>
<artifactId>jruby-engine</artifactId>
<version>1.1.7</version>
</dependency>

<dependency>
<groupId>org.python</groupId>
<artifactId>jython-standalone</artifactId>
<version>2.7.0</version>
</dependency>

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.7</version>
</dependency>

查看是否扩展成功

再举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void listEnginesName() {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
List<ScriptEngineFactory> factories = scriptEngineManager.getEngineFactories();
for (ScriptEngineFactory factory : factories) {
List<String> names = factory.getNames();
for (String name : names) {
System.out.println(name);
}
System.out.println(factory.getEngineName() + " -> " + factory.getLanguageName());
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
nashorn
Nashorn
js
JS
JavaScript
javascript
ECMAScript
ecmascript
Oracle Nashorn -> ECMAScript
groovy
Groovy
Groovy Scripting Engine -> Groovy
jruby
ruby
JRuby Engine -> ruby
python
jython
jython -> python