kotlin中使用脚本引擎

最近在找如何让kotlin检查自身代码更改,找了一圈发现找不到, 于是找到了很多脚本引擎. 脚本引擎相当于使用一个寄生语言, 而使用脚本引擎的语言相当于一个宿主语言. 脚本引擎有什么用呢?

  • ✔ 首先,可以使用其它编程语言来执行代码片段. 包括javascript, R语言,还有python之类.

  • ✔ 然后,可以使用脚本引擎来编排可视化工作流. 比如要想实现像cozedify工作流中的代码执行节点.

  • ✔ 第三, 可以用来执行复杂的DAG依赖任务,比如maven,gradle这样的依赖任务, 还比如简单的"m3u8"视频文件下载.

  • ✔ 第四, 可以用来识别代码更改. 代码更改之后,自动可以运行任务.

经过多次测试后,我发现脚本引擎支持的语言很多都是部分功能可用, 其它功能提供不完全.比如javascript引擎, 竟然大多引擎不提供运行命令行的功能. 找到的脚本引擎有: java自带的脚本引擎, Kotlin Multiplatform脚本引擎., Aviator脚本引擎, rhino脚本引擎, GraalVM脚本引擎, renjin脚本引擎

kotlin使用脚本引擎-寄生语言.jpg

java自带的脚本引擎

java自身提供一个Scripting API, 可以支持第三方语言以脚本的方式运行. java实现了一个默认的javascript语言的引擎.但是java11版本以后就不自带引擎了,需要单独添加引擎依赖才能用. 比如可以通过导入nashorn依赖或者Rhino依赖来单独支持javascript.

Aviator引擎

这是google开发的表达式引擎,能够快速求值表达式, 有一套自己的语法体系. 可以通过标准的接口向寄生语言添加宿主的功能.

只需要添加一个依赖,就能使用

<dependency>  
  <groupId>com.googlecode.aviator</groupId>  
  <artifactId>aviator</artifactId>  
  <version>5.3.3</version>  
</dependency>

kotlin代码如下

import com.googlecode.aviator.AviatorEvaluator  
import com.googlecode.aviator.runtime.function.AbstractFunction  
import com.googlecode.aviator.runtime.function.FunctionUtils  
import com.googlecode.aviator.runtime.type.AviatorDouble  
import com.googlecode.aviator.runtime.type.AviatorObject  

object TestAviator {  
    @JvmStatic  
    fun main(args: Array<String>) {  
        //注册函数  
        AviatorEvaluator.addFunction(AddFunction())  
        println(AviatorEvaluator.execute("add(1, 2)")) // 3.0  
        println(AviatorEvaluator.execute("add(add(1, 2), 100)")) // 103.0  
    }
}  
  
internal class AddFunction : AbstractFunction() {  
    override fun call(env: Map<String, Any>, arg1: AviatorObject, arg2: AviatorObject): AviatorObject {  
        val left = FunctionUtils.getNumberValue(arg1, env)  
        val right = FunctionUtils.getNumberValue(arg2, env)  
        return AviatorDouble(left.toDouble() + right.toDouble())  
    }  
  
    override fun getName(): String {  
        return "add"  
    }  
}

添加功能的过程很麻烦,但是对于宿主来说很安全. 有点像咱们养的猫狗, 给食物才能吃,不给的食物不能吃.

Rhino引擎

Rhino由Mozilla基金会所管理. 它最开始被加入到jdk1.6的默认脚本引擎中, 到jdk1.8中默认引擎被Nashorn取代. 到jdk11以后所有引擎都不默认了.

现在rhino还能使用, 使用需要添加maven依赖

<!-- https://mvnrepository.com/artifact/org.mozilla/rhino -->
<dependency>
    <groupId>org.mozilla</groupId>
    <artifactId>rhino</artifactId>
    <version>1.8.0</version>
</dependency>

可以直接使用宿主的类.

package org.xxx

import org.mozilla.javascript.Context
import java.io.File

fun main() {
    var ctx = Context.enter()
    var scope = ctx.initStandardObjects()
    var script = """
org.xxx.MyFile("d:/tt.tt").writeText("abcc")
"abc"
    """
    var result = ctx.evaluateString(scope, script, "script", 1, null)

    Context.exit()
}

class MyFile(var file: String){
    @JvmOverloads
    fun writeText(str: String){
        File(file).writeText(str)
    }
}

这就更像是寄生虫, 宿主的资源就是它的资源,尽管使用.

GraalVM引擎

需要添加两个依赖:

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>24.2.2</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>24.2.2</version>
</dependency>

这个引擎说明上写,可以通过import方式来导入".mjs"文件模块. 也可以通过打开选项的方式使用"require"导入模块,但是我测试了一下, 只能通过动态导入import方式导入".mjs"文件模块. 但是导入不了nodejs的模块.

Nashorn引擎

需要引入一个依赖,然后就可以在kotlin中使用了

<dependency>
    <groupId>org.openjdk.nashorn</groupId>
    <artifactId>nashorn-core</artifactId>
    <version>15.6</version>
</dependency>

使用方法和Rhino差不多, 一个kotlin代码例子:

package org.xxx

import javax.script.ScriptEngineManager
import javax.script.ScriptException

class MyExample {
    @JvmOverloads
    fun hello(str: String= ""): String{
        return("hello $str")
    }
}

fun main(args: Array<String>) {

    try {
        val manager = ScriptEngineManager()
        val engine = manager.getEngineByName("JavaScript")
        engine.eval("""  
//这里可以导入maven依赖包
var MyExample = Java.type("org.xxx.MyExample")  
var rst = new MyExample().hello("world");  
        """);
        println(engine.get("rst"))
    } catch (e: ScriptException) {
        e.printStackTrace()
    }
}

Renjin引擎

这个是R语言运行的脚本引擎, R语言是统计学相关的语言,非常强大. 但是由于kotlin的问题,导致代码中使用美元符号要使用多次.

添加依赖就可以直接使用renjin:

<dependency>
  <groupId>org.renjin</groupId>
  <artifactId>renjin-script-engine</artifactId>
  <version>0.9.2726</version>
</dependency>

注意下载需要添加特殊maven仓库:

<repository>
  <id>temp repo</id>
  <url>https://nexus.bedatadriven.com/content/groups/public/</url>
</repository>

在kotlin的使用方法:

import org.renjin.script.RenjinScriptEngineFactory
import java.io.File
import javax.script.ScriptEngine

object TryRenjin {
    @Throws(Exception::class)
    @JvmStatic
    fun main(args: Array<String>) {
        // create a script engine manager:
        val factory: RenjinScriptEngineFactory = RenjinScriptEngineFactory()
        // create a Renjin engine:
        val engine: ScriptEngine = factory.getScriptEngine()

        var result = engine.eval("""
import(org.xxx.MyFile)
file = MyFile${'$'}new("d:/tt.txt")
file${'$'}writeText("tt")
"def"
        """)
        println(result)
    }
}

class MyFile(var file: String){
    @JvmOverloads
    fun writeText(str:String) {
        File(file).writeText(str)
    }
}

本来就一个美元符号$, 为什么要写成那么复杂的代码${'$'}.

脚本引擎提供的功能

虽然脚本引擎能够使用宿主的执行环境来执行各种功能, 但是能够提供自生语言的功能,使用起来也方便一点吧.下面列出各种引擎给出基本的功能.

基本功能 Nashorn Aviator Rhino GraalVM Renjin
Date类型 ✔(不全)
sleep多少秒
运行命令行
导入寄生依赖包
导入宿主依赖包
读写文件

能够导入宿主依赖包 就是说寄生语言可以导入宿主语言的所有maven依赖.
导入寄生依赖包 就是说寄生语言可以使用自身import和require语句导入包

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容