Executing JavaScript with Nashorn in Java 15+ Replacing jjs
2025, Feb 23
jjs nashorn
요구사항
- 기존에는 jjs를 통해서 리눅스 서버에서 Javascript 파일을 실행시켜왔는데, java 11에는 jjs가 deprecated되고 java15 이후부터 jjs가 삭제되어서 사용할 수 없기 때문에 대체 방식을 고민해야했다.
jjs 대체 기술 검토
1. 검토 방향
- java8부터 17+까지 호환가능한 방식이여야한다.
- 설치한 서버에서 컴파일없이 수정이 가능해야한다.
2. 방식
- 1안) java의 jjs가 아닌, jjs 라이브러리 파일을 위치해놓고 별도 주입
- 단점:
- Java 8의 Nashorn 엔진은 Java 15 이후의 최신 JVM과 완전히 호환되지 않을 수 있음.
- 단점:
- 2안) GraalVM
- 단점
- GraalVM (21.x 이상)은 Java 8을 직접 지원하지 않음.
- 기존 프로젝트와 통합이 어려움
- 단점
- 3안) Mozilla Rhino (JavaScript 엔진)
- 단점
- Rhino보다는 Nashorn사용 권장(속도 측면 비추)
- 단점
- 4안) javascript를 통해 실행시키는 방법자체를 변경
- jjs의존하지 않는 방식으로 논의 → curl로 실행하는 쉘 스크립트 작성 등
- 단점
- 유지보수 어려움
- 5안) org.openjdk.nashorn 사용 (택) - 상세설명은 하단에서..
- 논외
- python, HttpURLConnection, nodejs ..
openjdk.nashorn 적용
1. 정리
- Nashorn이란?
- Nashorn은 JDK 8에서 처음 도입되었고,
- JDK 11에서 Deprecated(사용 중단 예정) 되었으며,
- JDK 15에서 완전히 제거되었다.
- openjdk.nashorn은
- 15 이후부터는 깃헙 저장소에서 GPLv2 with Classpath Exception 라이선스로 유지됨(JAR 형태로 포함하여 상용 프로젝트에서 사용 가능.)
- 자바버전별 호환
- nashorn 15.2~15.6의 경우org/openjdk/nashorn/api/scripting/NashornScriptEngineFactory 최소 버전이 java11이여서 8에서는 실행시킬수 없다.
- 15.1, 15.1.1은 최소 java15이상만 가능 (특이하다.)
- 15.0은 8 가능
- 단점
- OpenJDK Nashorn 프로젝트는 현재 활발히 유지보수되지 않음.
2. 설계
- 깃헙으로 오면서(15이후), 독립실행형 (Main)클래스가 제거되었다.
- 그래서 openjdk-nashorn의 ScriptEngineManager을 통해 실행시키는 코드 구현 필요하다.
openjdk-nashorn 버전별 자바와의 호환 차이
openjdk.nashorn 버전 런타임 최소버전 15.1, 15.1.1 java15 15.2~15.6(latest) java11 15.0 java8 - 자바15이후부턴 security privilege하는 부분이 문제가 있어서 15.0사용 불가
- 자바15이후 지원을 위해 15.6으로 하면, 사용가능하지만
- 반대로 자바 낮은 버전에서 사용못함
자바8에서는 jjs가 있어서 잘되지만 자바17에서 하면 “engine” is null 오류
Error: No script nashorn engine found java.lang.NullPointerException: Cannot invoke "javax.script.ScriptEngine.eval(java.io.Reader)" because "engine" is null at NashornExecutor.main(NashornExecutor.java:26)
- nashorn 15.0은 java17에서 nashorn모듈네임(“engine” is null 오류)을 찾을수가 없고, 그게 java 15부터 deprecated되어서 15.1부터는 dependency를 걸면 java8에서 unsupported발생한다.
- 결론은
- 15미만까지는 기존처럼 java의 jjs를 사용하고
- java15+의 경우는 Main메서드를 통해 openjdk-nashorn engine을 통해 실행하도록 변경
- 방식은 외부 라이브러리 참조방식과 내부 의존성 추가 방식
3. 구현
1안) 라이브러리 외부 참조방식
- 필요한 라이브러리
nashorn-core-15.x.jar
asm-9.5.jar
,asm-util-9.5.jar
Main메서드가 실행될 때 리플렉션을 통해 외부 라이브러리 참조하도록 함
mport java.io.File; import java.io.FileReader; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; public class NashornExecutor { public static void main(String[] args) { try { Class<?> clazz = Class.forName("org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory"); ScriptEngineFactory factory = (ScriptEngineFactory) clazz.getDeclaredConstructor().newInstance(); engine = factory.getScriptEngine(); System.out.println("ScriptEngine created: " + engine); // JavaScript 코드 실행 } catch (Exception ex) { ex.printStackTrace(); } } }
실행
cmd) java -cp "nashorn-core-15.0.jar:asm-9.5.jar:asm-util-9.5.jar" \ NashornExecutor test.js
2안) 내부 maven 의존성 추가방식
pom.xml에 의존성 추가
<dependency> <groupId>org.openjdk.nashorn</groupId> <artifactId>nashorn-core</artifactId> <version>15.6</version> </dependency>
Main메서드를 통해 nashorn엔진 실행 코드 작성
import java.io.File; import java.io.FileReader; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; public class NashornExecutor { public static void main(String[] args) { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("nashorn"); if (engine == null) { System.out.println("Error: nashorn engine not found."); } try { engine.eval(new FileReader(new File("javascript file"))); } catch (Exception e) { e.printStackTrace(); } } }
실행
- java8 ~ java15 미만 버전
cmd> jjs -cp {MAVEN_TARGET}\nashornexecutor-1.0.0-SNAPSHOT.jar test.js
- java15 이후 버전
cmd> java -cp {MAVEN_TARGET}\nashornexecutor-1.0.0-SNAPSHOT.jar \ NashornExecutor test.js