욱'S 노트

Java - Nashorn 본문

JAVA/Pleasure

Java - Nashorn

devsun 2016. 3. 10. 13:53

Using Nashorn


Nashorn은 자바 프로그램에서 사용할 수 있는 자바스크립트 엔진이다. Nashorn은 커맨드라인에서 사용할 수 있는데 jjs라는 커맨드로 실행시킬 수 있다.


$ jjs

jjs> print('Hello World');


이번 튜토리얼은 자바코드에서 nashorn을 사용하는 것에 포커스를 맞춘다. 다음은 간단한 HelloWorld 예제이다.


자바에서 자바스크립트를 수행하기 위해서 nashorn 스크립트 엔진을 먼저 생성해야 한다.

ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
scriptEngine.eval("print('Hello world');");


자바 스크립트 코드는 위와 같이 직접 문자열로 전달할 수도 있고 FileReader를 통해 js 스크립트 파일을 전달 할 수 있다.

ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
scriptEngine.eval(new FileReader("test.js"));


Nashorn 스크립트는 ECMAScript 5.1 기반이지만 곧 ECMAScript 6을 지원할 것이다.


Nashorn의 현재 전략은 ECMAScript 스펙을 따르는 것이다. 


Nashorn의 다양한 언어와 API 확장은 ECMAScript 표준에 정의되어 있다. 그러나 먼저 자바와 자바스크립트간의 코드가 어떻게 동작하는지를 살펴보자.


Invoking Javascript Functions from Java


나스호른에서는 자바코드로부터 스크립트 파일에 정의된 자바스크립트 함수를 직접 호출할 수 있다. 우리는 자바 오브젝트를 함수의 인자로 전달할 수 있고 함수의 호출결과를 전달 받을 수 있다.


다음은 자바에서 호출할 자바스크립트 함수의 예이다.

var func1 = function(name) {
print("Hi there from javascript," + name);
return "greetings from javscript";
}

var func2 = function(object) {
print("JS class definition : " + Object.prototype.toString().call(object));
}


먼저 함수를 호출하기 위해서는 자바 스크립트 엔진을 invocable로 캐스팅해야된다. Invocale 인터페이스는 NashornScriptEngine에 구현되어 있으며 주어진 이름의 자바스크립트 함수를 호출할 수 있는 invokeFunction 함수가 정의되어 있다.

ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
scriptEngine.eval(new FileReader(filePath));

Invocable invocable = (Invocable)scriptEngine;

Object result = invocable.invokeFunction("func1", "Peter Parker");
System.out.println(result);
System.out.println(result.getClass());


// Hi there from javascript,Peter Parker

// greetings from javscript

// class java.lang.String


코드 실행의 결과는 콘솔에 세줄로 출력된다. 


이제 두번째 함수를 자바오브젝트를 전달해서 호출해보자.

invocable.invokeFunction("func2", new Date());

// JS class definition : [object java.util.Date]


invocable.invokeFunction("func2", LocalDateTime.now());

// JS class definition : [object java.time.LocalDateTime]


invocable.invokeFunction("func2", new Person());

// JS class definition : [object com.kakao.warriv.process.application.mapping.NashornTest$Person]


자바 오프젝트의 어떤 타입 정보도 손실없이 자바스크립트에 전달된 것을 알 수 있다. 스크립트나 네이티브하게 JVM에서 실행되기 때문에 우리는 자바 API 또는 외부 라이브러리를 완전히 나스호른에서 이용할 수 있다.


Invoking Java Methods from Javascript


자바스크립트로부터 자바 메소드를 호출하는 것도 매우 쉽다. 먼저 static한 자바 메소드를 하나 정의해보자.

public static String func1(String name) {
System.out.format("Hi there from Java, %s\n", name);
return "greetings from java";
}

자바 클래스들은 Java.type API로 자바스크립트에서 호출될 수 있다. 이것은 자바 코드의 import와 비슷하다. 자바 타입에 정적으로 fun1이 정의되어 있기 때문에 우리는 인스턴스를 먼저 생성할 필요가 없다.

var NashornTest = Java.type("mapping.NashornTest");
var result = NashornTest.func1("John Doe");
print(result);


// Hi there from Java, John Doe

// greetings from java


나스호른에서 자바 메소드를 호출했을때 자바오브젝트 - 네이티브 자바스크립트 타입간의 어떻게 타입 컨버젼을 처리할까? 다음의 간단한 예제를 따라해보자.


다음은 단순히 메소드 파라미터의 클래스를 출력하는 자바 메소드이다.

public static void func2(Object object) {
System.out.println(object.getClass());
}

타입 컨버젼을 이해하기 위해 다음과 같은 자바스크립트 코드들을 실행해보자.


MyJavaClass.fun2(123);

// class java.lang.Integer


MyJavaClass.fun2(49.99);

// class java.lang.Double


MyJavaClass.fun2(true);

// class java.lang.Boolean


MyJavaClass.fun2("hi there")

// class java.lang.String


MyJavaClass.fun2(new Number(23));

// class jdk.nashorn.internal.objects.NativeNumber


MyJavaClass.fun2(new Date());

// class jdk.nashorn.internal.objects.NativeDate


MyJavaClass.fun2(new RegExp());

// class jdk.nashorn.internal.objects.NativeRegExp


MyJavaClass.fun2({foo: 'bar'});

// class jdk.nashorn.internal.scripts.JO4


프리미티브 자바스크립트 타입은 적절한 자바 랩퍼 클래스로 컨버팅된다. 대신에 네이티브 자바스크립트 오브젝트는 인터널 어댑터 클래스로 표현된다. jdk.nashorn.internal 클래스들의 목적이 변경을 위한 것임을 명심하자. 그래서 클라이언트 코드에 이러한 클래스에 대항하는 프로그램을 하지 않아야 한다.



ScriptObjectMirror


네이티브 자바스크립트 오브젝트를 자바에 전달할때 ScriptObjectMirror 클래스를 활용해 내부의 자바스크립트 오브젝트를 표현한다. ScriptObjectMirror 맵 인터페이스를 구현하고 있으며 내부에 jdk.nashorn.api를 패키징하고 있다. 이 패키지의 클래스들은 클라이언트 코드에서 사용될 수 있다.


다음 샘플은 오브젝트 파라미터 타입을 ScriptObjectMirror로 변경한다.. 그래서 전달된 자바스크립트 오브젝트로부터 몇가지 정보를 추출할 수 있다.

public static void func3(ScriptObjectMirror mirror) {
System.out.println(mirror.getClassName() + ":" + Arrays.toString(mirror.getOwnKeys(true)));
}


오브젝트 hash가 이 메소드에 전달되었을때 해당 프로퍼티들을 자바에서 접근할 수 있다.

NashornTest.func3({foo : "foo", bar : "bar"})


// Object: [foo, bar]


우리는 또한 자바에서 자바스크립트의 멤버 함수를 호출할 수 있다. 먼저 firstName과 lastName 프로퍼티를 가진 Person 자바스크립트 타입을 정의해보자.

function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;

this.getFullName = function() {
return this.firstName + " " + this.lastName;
}
}


자바 스크립트 메소드 getFullName은 ScriptObjectMirror callMember() 메소드에 의해 호출할 수 있다.

public static void func4(ScriptObjectMirror person) {
System.out.println("Full Name is: " + person.callMember("getFullName"));
}


새로운 인물을 자바 메소드에 전달한 다음 그 결과를 콘솔에서 확인해보자.

var person1 = new Person("Peter", "Parker");
NashornTest.func4(person1);


// Full Name is: Peter Parker


Language Extensions


나스호른은 ECMAScript 표준에서 제공하는 다양한 언어와 API 확장판을 지원한다.


Typed Arrays


네이티브 자바스크립트 배열은 untyped이다. 나사호른은 자바스크립트에서 typed 자바 배열을 가능하게 한다.

var IntArray = Java.type("int[]");

var array = new IntArray(5);

array[0] = 5;
array[1] = 4;
array[2] = 3;
array[3] = 2;
array[4] = 1;

try {
array[5] = 23;
} catch (e) {
print(e.message); // Array index out of range: 5
}

array[0] = "17";
print(array[0]); // 17

array[0] = "wrong type";
print(array[0]); // 0

array[0] = "17.3";
print(array[0]); // 17

int[] 배열은 실제 자바 int 배열처럼 동작한다. 그러나 추가적으로 나스호른은 묵시적은 타입 변경을 수행한다. 문자열은 꽤 핸디가 있는 자동 변환이 일어난다.


Collections and For Each


배열대신에 자바 컬렉션을 사용할 수 있다. 먼저 Java.type을 통해 새로운 인스턴스를 생성하자.

var ArrayList = Java.type("java.util.ArrayList");
var list = new ArrayList();

list.add("a");
list.add("b");
list.add("c");

for each (var el in list) print(el);

// a

// b

// c


컬렉션이나 배열을 탐색하기 위해 나스호른은  for each이 제공한다. 이것은 자바의 foreach같이 수행된다.


여기 또 다른 foreach 예제이다. HashMap을 사용해보자.

var map = new java.util.HashMap();
map.put("foo", "var1");
map.put("bar", "var2");

for each (var e in map.keySet()) print(e); // foo, bar
for each (var e in map.values()) print(e); // var1, var2


Lambda expressions and Streams


모든 사람은 람다와 스트림을 사랑한다. 그래서 나스호른은 지원한다. 비록 ECMAScript의 람다와 스트림은 자바 8보다 부족하지만 우리는 람다를 사용할 수 있다.

var list2 = new java.util.ArrayList();
list2.add("ddd2");
list2.add("aaa2");
list2.add("bbb1");
list2.add("aaa1");
list2.add("bbb3");
list2.add("ccc");
list2.add("bbb2");
list2.add("ddd1");

list2.stream()
.filter(function(el) {
return el.startsWith("aaa");
})
.sorted()
.forEach(function(el) {
print(el);
});

// aaa1, aaa2


Extending classes


자바 타입은 단순히 Java.extend 확장으로 확장될 수 있다. 다음 예제를 보면 스크립트에서 멀티 스레드 코드조차 실행할 수 있음을 알 수 있다.

var Runnabe = Java.type("java.lang.Runnable");

var Printer = Java.extend(Runnabe, {
run : function() {
print("printed from a seperate thread.")
}
});

var Thread = Java.type("java.lang.Thread");
new Thread(new Printer()).start();

new Thread(function() {
print("printed from another therad.");
}).start();

// printed from a separate thread

// printed from another thread


Parameter overloading


메소드와 함수는 point notation 또는 square braces notation에 의해 호출될 수 있다.

var System = Java.type('java.lang.System');
System.out.println(10); // 10
System.out["println"](11.0); // 11.0
System.out["println(double)"](12); // 12.0


옵션 파라미터 println(doublue)을 전달함으로써 오버로드된 정확한 메소드를 호출할 수 있다.


Java Beans


명시적인 getter나 setter를 호출하는 대신에 프로퍼키의 이름으로 자바 빈의 프로퍼티를 접근할 수 있다.

var Date = Java.type("java.util.Date");
var date = new Date();
date.year += 1900;
print(date.year); // 2016


Function Literals


단순한 한 라인짜리 함수는 블레이스를 생략할 수 있다.

function sqr(x) x * x;
print(sqr(3)); // 9


Binding properties


두 다른 오브젝트 프로퍼티들은 서로 바운드될 수 있다.

var o1 = {};
var o2 = { foo: 'bar'};

Object.bindProperties(o1, o2);

print(o1.foo); // bar
o1.foo = 'BAM';
print(o2.foo); // BAM


Trimming strings


문자열을 트림할 수 있다.

print("   hehe".trimLeft());            // hehe
print("hehe ".trimRight() + "he"); // hehehe


Whereis


이 경우 어느 위치에 실행을 하는지 알 수 있다.

print(__FILE__, __LINE__, __DIR__);


Import Scopes


때때로 자바 패키지의 한꺼번에 임포트하는 것은 유용하다. 이런 경우 우리는 JavaImporter 클래스를 이용할 수 있다. 

var imports = new JavaImporter(java.io, java.lang);
with (imports) {
var file = new File(__FILE__);
System.out.println(file.getAbsolutePath());
// /path/to/my/script.js
}


Convert arrays


java.util과 같이 몇가지 패키지는 Java.type 이나 JavaImporter를 사용하지 않고 직접 참조할 수 있다.

var list = new java.util.ArrayList();
list.add("s1");
list.add("s2");
list.add("s3");


다음 코드의 자바의 리스트를 네이티비 자바스크립트 오브젝트 배열로 변경하는 것이다.

var jsArray = Java.from(list);
print(jsArray); // s1,s2,s3
print(Object.prototype.toString.call(jsArray)); // [object Array]


또 다른 방식은 다음과 같다.

var javaArray = Java.to([3, 5, 7, 11], "int[]");


Calling Super


자바스크립트에서 오버라이드된 멤버를 접근하는 것은 일반적으로 어색하다. ECMAScript에서는 자바처럼 super가 지원되지 않기 때문이다. 나스호른에서는 이러한 사항을 지원한다.


먼저 자바 코드에 슈퍼타입을 정의해보자.

public class SuperRunner implements Runnable {
@Override
public void run() {
System.out.println("super run");
}
}


다음은 자바스크립트에서 SuperRunner를 오버라이드할 것이다.

var SuperRunner = Java.type("mapping.SuperRunner");
var Runner = Java.extend(SuperRunner);

var runner = new Runner() {
run: function() {
Java.super(runner).run();
print('on my run');
}
}
runner.run();

// super run

// on my run


Java.super 확장을 이용해서 SuperRunner.run()의 슈퍼 메소드를 호출할 수 있다.


Loading scripts


추가적인 스크립트를 로딩하는 것은 매우 쉽다. 우리는 load함수를 통해 로컬 또는 리모트 스크립트를 로딩할 수 있다.


Underscore.js는 많은 웹프론트에서 사용된다. 나스호른에서 Underscore를 사용해보자.

load('http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js');

var odds = _.filter([1, 2, 3, 4, 5, 6], function (num) {
return num % 2 == 1;
});

print(odds); // 1, 3, 5


외부 스크립트도 같은 자바스크립트 컨텍스트에서 평가된다. 그래서 우리는 underscore의 변수들을 직접 접근할 수 있다. 스크립트 로딩을 잠재적으로 우리의 코드에 영향을 줄 수 있다. 각 변수명이 오버래핑될 수 있기 때문이다.


이러한 문제는 로딩 스크립트 파일을 새로운 글로벌 컨텍스트로 전달하면 된다.

loadWithNewGlobal('script.js');


여기까지다.


이 가이드가 나스호른 자바스크립트 엔진을 여행하는데 도움이 되었으면 좋겠다. 


Keep on coding!


출처 : http://winterbe.com/posts/2014/04/05/java8-nashorn-tutorial/

'JAVA > Pleasure' 카테고리의 다른 글

Java - Stream  (0) 2016.01.14
Java - Lamda expression  (0) 2016.01.13
NIO란?  (0) 2015.04.17
Stack과 heap의 차이점  (0) 2015.01.12
Comments