본문 바로가기
Language/Kotlin

[Kotlin] Java Scripting API (JSR-223)

by 돈코츠라멘 2019. 10. 19.

JSR-223

Kotlin은 Java scripting engine을 사용하기 위한 툴로 Java Scripting API를 지원한다. 원래 우리의 프로젝트에서는 Java scripting engine으로 Nashorn을 사용했었는데, 이게 jdk11부터 deprecated(관련 링크 - https://openjdk.java.net/jeps/335) 되면서 다른 대안이 필요해졌다. 그래서 서치하다 보니 Kotlin에서 제공하는 Java Scripting API(JSR-223)가 있었다. Kotlin 1.1 버전부터 지원한다. 우리는 이미 백엔드 서버 개발을 Kotlin으로 하고 있었기 때문에 큰 변경 없이 이를 사용할 수 있었다.

 

제공하는 기능은 Nashorn과 거의 유사하다. 사용자가 작성한 스크립트 코드를 JVM상에서 동작하는 application이 런타임에 바로 가져와서 실행할 수 있다. 또한 JSR-223은 javascript 뿐만 아니라 Kotlin script도 지원한다. 그래서 Kotlin application에서도 바로 Kotlin script를 런타임에 가져와서 실행할 수 있다.

 

/추가/ 예시에서 사용한 js extension은 내부적으로 Nashorn을 사용하고 있는것을 확인하였다. 따라서 Nashorn의 대체재로 사용하지는 맙시다.

 

절망의 stacktrace

이걸 보고 혹시나 하는 마음에 서치해보니 내부적으로 Nashorn을 사용하고 있었다. 뭔가를 새로 도입할 땐 꼼꼼하게 확인하도록 해야겠다. 이제 다른 스크립트 엔진을 또 찾아봐야지.

 

script-example

Source Tree

프로젝트는 Gradle로 구성하였다.

script-example/
 ├── src/
 │   ├── main/
 │   │   ├── kotlin/
 │   │   │   └── .../ScriptUsecase.kt
 │   │   └── resource/
 │   │       └── services/
 │   │           └── javax.script.ScriptEngineFactory
 │   └── test/
 │   │   └── kotlin/
 │   │       └── .../ScriptUsecaseTest.kt
 └── build.gradle

build.gradle

필요한 라이브러리를 추가한다.

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-script-util:$kotlinVersion"
    implementation "org.jetbrains.kotlin:kotlin-script-runtime:$kotlinVersion"
    implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinVersion"
}    

ScriptUsecase.kt

ScriptEngineManager로부터 사용할 엔진을 이름으로 불러온다. 여기서 어떤 엔진을 사용 가능한지 확인하고 싶다면 ScriptEngineManager().engineFactories.forEach { println(it.extensions) }를 실행하여 확인해본다. 보통 ['js'], ['kts'] 두가지의 엔진을 확인할 수 있을 것이다. 'js'는 javascript, 'kts'는 kotlin script를 실행한다. 예시에서는 'js'를 사용한다.

class ScriptUsecase {
    fun eval(script: String): Any? {
        val engine = ScriptEngineManager().getEngineByExtension("js")!!
        return engine.eval(script)
    }
}

javax.script.ScriptEngineFactory

하나 주의해야 할 점은 resource/service 디렉터리 아래에 javax.script.ScriptEngineFactory라는 이름의 파일이 들어있어야 한다. 여기에 사용할 script engine factory를 정의해둬야 한다. 가장 기본의 설정으로 사용하려면 아래와 같이 작성하면 되고, 만약 custom script engine factory를 사용하고 싶으면 여기에 기입하도록 한다.

org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory

ScriptUsecaseTest

js 문법에 맞게 스크립트를 작성해서 이를 넘긴다.

class ScriptUsecaseTest {

    val scriptUsecase = ScriptUsecase()

    @Test
    fun simpleEvalTest() {
        val str1 = "x = 1.0; y = 2.0; x + y"
        print(scriptUsecase.eval(str1))
        assertEquals(3.0, scriptUsecase.eval(str1)) 
    }

}

 

Conclusion

사용자체는 굉장히 간단하고, 런타임에서 넘겨받은 스크립트를 실행하기 때문에 활용 분야도 넓다. 하지만 아직 성능에 관해서 부정적인 의견이 많다. 우리 프로젝트에서도 성능에 관한 검증을 거친 후에 도입할지 말지를 결정해야 할 것 같다.

 

댓글