의역, 오역, 직역이 있을 수 있음을 알려드립니다.
이 포스트는 원저자의 동의를 얻어 한글로 번역한 내용입니다.This post is a translation of this original article Riding the Nashorn: Programming JavaScript on the JVM by Niko Köbler from http://www.n-k.de
Original article: Riding the Nashorn: Programming JavaScript on the JVMAuthor: Niko Köbler
Author Email: niko@n-k.de
Author Blog: http://www.n-k.de
Author Twitter: @dasniko
Nashorn은 Java8 버전 부터 Java Virtual Machine의 공식 JavaScript 엔진입니다.
ECMAScript 5.1 사양에 따라 구현되었으며 Google V8 (Node.js의 스크립트 엔진)과 경쟁합니다.
Nashorn은 런타임 동안 JavaScript를 Java 바이트 코드로 컴파일하기 때문에 Java와 JavaScript의 사이의 높은 상호 운용성을 제공합니다.
이 글은 Nashorn에서 가장 많이 사용 되는 Option, Function, Feature와 그리고 Use case에 대해 살펴 보려고합니다.
이 튜토리얼을 읽은 후에는 일반 Java 프로그램에서 Nashorn을 이용하여 JavaScript 동적 스크립팅 기능을 사용할 수 있을 것으로 기대합니다.
Nashorn을 공부하기 위한 또 다른 좋은 방법은 Official Project Nashorn 웹 사이트이며, 공식 웹사이트에는 메일 링리스트, 블로그 및 wiki 페이지들이 있습니다. 바로 확인해 보세요.
JDK 8의 Nashorn은 위에 언급 한대로 ECMAScript 5.1 사양을 구현했습니다.
Nashorn의 전략은 ECMAScript 사양을 따르는 것입니다. 그렇기 때문에 Nashorn의 주요 후속 버전에서는 ECMAScript 2015 spec을 구현할 예정입니다.
1. Nashorn의 Command Line Interface (CLI)
Nashorn은 jjs
라는 명령행 클라이언트(CLI)를 제공합니다.
이 jjs
는 $JAVA_HOME/bin에 위치해 있지만, 바로가기/링크/Path등의 설정이 Java 8 설치 기능에 포함되어 있지 않습니다. 따라서 jjs
로 작업하기를 원한다면 아래와 같이 jjs
링크를 설정해야 합니다.
|
|
환경변수의 PATH에
$JAVA_HOME/bin
을 추가하면 따로 설정할 필요가 없습니다.
이제 jjs
쉘에서 직접 JavaScript 코드를 실행할 수 있습니다.
그리고 jjs
를 이용하여 파일로 저장되어 있는 JavaScript를 로드하고 실행할 수 있습니다.
test.js
|
|
jjs 종료
jjs
클라이언트는 아래와 같은 함수를 호출하여 종료할 수 있습니다.
|
|
도움말 / 옵션
-help 옵션을 사용하면 jjs
의 모든 옵션이 나열됩니다.
|
|
1.1. Scripting 모드
Nashorn은 ECMAScript 5.1 사양 외에도 자체 구문및 확장 API를 구현했습니다. (다음 장 참조).
이러한 확장 기능의 대부분은 스크립팅 모드에서만 사용할 수 있습니다.
스크립팅 모드는 cli의 -scripting 옵션을 사용하여 활성화 됩니다.
|
|
스크립팅 모드에서 Nashorn은 Shell 스타일의 ‘#’을 이용한 한 줄 전체 주석을 사용할 수 있습니다.
test.js
1.2 Shebang
Shebang은 Unix 계열 shell script에서 script 해석 인터프리터를 명시한 것을 말합니다.
대부분의 Unix 계열 shell script의 첫번째 라인에 아래와 같은 부분이 있는데 이게 Shebang입니다.
|
|
Nashorn은 Shebang 스크립팅을 지원합니다.
따라서 아래와 같이 기존 방식처럼 JavaScript 파일을 호출하는 대신
test.js
|
|
첫 줄에 Shebang과 함께 다음과 같은 JavaScript 파일을 작성할 수 있습니다.
다른 shell 스크립트와 마찬가지로 실행 파일로 만들고 명령 행에서 실행할 수 있습니다.
|
|
이 장의 시작 부분에서 언급했듯이
jjs
에 대한 링크가 있어야합니다.
Shebang Nashorn 스크립트를 실행하면 스크립팅 모드가 자동으로 활성화됩니다.
2. Java에서 Nashorn JavaScript 엔진 사용
Nashorn 엔진은 JSR-223(Scripting for the Java Platform)의 자바 스크립트 구현이며 javax.script API의 구현체 입니다.
따라서 Java에서 JavaScript 코드를 사용하기 위해서는 Nashorn javax.script.ScriptEngine을 생성 해야 합니다.
|
|
(1) 여기에서 사용할 수있는 Nashorn 엔진 이름은 “nashorn”, “javascript”및 “js”입니다.
위에서 볼 수 있듯이 JavaScript 코드는 엔진 객체의 eval() 메서드에 문자열로 전달하여 직접 실행할 수 있습니다.
또는 파일을 가리키는 FileReader 객체를 전달하여 .js
파일을 구문 분석(및 실행)을 할 수 있습니다.
|
|
2.1. Java에서 JavaScript 함수 호출
Java 프로그램 내부에서 단일 JavaScript 구문 또는 JavaScript 파일을 실행하거나 JavaScript 함수를 호출할 수도 있습니다.
또한 Java 객체를 JavaScript 함수의 arguments로 전달하고, JavaScript 함수에서 호출한 Java 메서드로 데이터를 반환할 수 있습니다.
example.js
위와 같이 작성된 example.js에 정의된 sayHello 함수를 호출하려면,
먼저 아래의 코드에서 보는 바와 같이 engine 객체를 NashornScriptEngine
에서 구현된 Invocable
Interface로 캐스팅해야합니다.Invocable
Interface는 invokeFunktion() 메서드를 제공하는데, 이 메서드는 입력값으로 받은 JavaScript 함수이름과 arguments를 이용해 Javascript를 실행할수 있습니다.
|
|
위에 기술한 코드는 콘솔에 세 줄을 출력합니다.
JavaScript의 sayHello 함수의 print()는 System.out에 pipe되어 출력하고, Java 코드의 두개 System.out.println() 메소드에서 sayHello 함수의 리턴값과 리턴값의 Class이름을 출력합니다.
2.2. Javascript에서 Java method 호출
JavaScript 코드에서 Java 메소드를 호출하거나 실행하는 반대의 경우도 간단합니다.
Java의 두 종류의 메소드를 가정해 보겠습니다.
MyJavaClass.java
Java 클래스는 Java.type API 확장을 통해 JavaScript에서 참조할 수 있습니다.
이는 Java의 import
문과 유사합니다. Java 클래스를 참조한 후에 sayhello() 메서드와 같은 static method는 바로 호출하여 결과를 System.out에 출력할 수 있습니다. 또한 add()와 같은 일반 메서드들은 class의 인스턴스를 생성하여 호출할 수 있습니다.
|
|
2.2.1. Nashorn의 type 변환
아래의 간단한 예제를 통해 JavaScript에서 Java 메서드를 호출할 때 Nashorn이 Java와 JavaScript 간의 형식 변환을 처리하는 방법을 확인할 수 있습니다.
|
|
|
|
- Primitive JavaScript type은 적절한 Java 래퍼 클래스로 변환됩니다.
- Native JavaScript 객체는 ScriptObjectMirror 내부의 각각 어댑터 클래스로 표현됩니다.
jdk.nashorn.internal의 클래스는 추후 변경 될 수 있으므로 클라이언트 코드에서 해당 클래스에 대해 프로그램을 작성하면 안됩니다.
2.3. ScriptObjectMirror
ScriptObjectMirror는 jdk.nashorn.api의 일부이며 기본 Javascript 내부 클래스 대신 Java 클라이언트 코드에서 사용하기위한 것입니다. 이 미러 객체는 JavaScript의 기본 객체의 표현입니다. 또한 Java Map 인터페이스를 구현하였으며, 객체, 메서드 및 속성에 대한 액세스를 제공합니다.
위의 예제를 조금 바꿔 보겠습니다.
마지막 네개의 JavaScript 함수 호출 (number, date, regexp 및 object literal)을 사용하여이 메서드를 호출하면 다음과 같습니다.
|
|
결과는 다음과 같습니다.
|
|
또한 Java에서 JavaScript 객체의 멤버 함수를 호출할 수 있습니다.firstName
, lastName
속성과 getFullName() 함수를 가진 JavaScript Person 타입을 가정해 보겠습니다.
|
|
Javascript의 getFullName() 함수는 ScriptObjectMirror 클래스의 callMember() 메서드를 이용해 호출할수 있습니다.
|
|
person 객체를 Java 메서드에 전달하면 콘솔에 원하는 결과가 표시됩니다.
|
|
2.4. Script engine 옵션
nashorn.args
system property을 사용하여 Nashorn 스크립트 엔진을 customize 할 수 있습니다. 예를 들어 아래와 같이 스트립팅 모드 사용옵션을 -Dnashorn.args = ...
을 이용하여 지정할 수 있습니다.
|
|
customize 옵션을 파라메터로 넘겨 Nashorn engine을 생성할 수도 있습니다. 이 경우 아래 예제와 같이 NashornScriptEngineFactory를 직접 인스턴스화 해야합니다.
|
|
jjs -help를 호출하여 사용 가능한 옵션을 확인할 수 있습니다.
2.5. Bindings / Context
ScriptContext는 하나 이상의 jsr223 “scope”이 Binding 되어 포함되어 있습니다. 그리고 디폴트 scope
으로 ENGINE_SCOPE
와 GLOBAL_SCOPE
의 두 가지가 있습니다.
ENGINE_SCOPE
GLOBAL_SCOPE
Nashorn 엔진이 생성되고 default context를 생성합니다.
|
|
default context
의 ENGINE_SCOPE
은 ECMAScript “global” object를 래핑한 인스턴스입니다. 이 object는 최상위 script의 “this”입니다. 따라서 이 scope
객체에서 “Object”, “Math”, “RegExp”, “undefined”와 같은 ECMAScript 최상위 객체를 액세스할 수 있습니다. 또한 GLOBAL_SCOPE
은 같은 ScriptEngineManager로 작성된 모든 엔진 사이에 공유됩니다.context
에 변수들을 저장할 수 있지만 scope은 선택 사항이며 기본값은 ENGINE_SCOPE입니다.
|
|
Nashorn이 변수를 검색할 때 ENGINE_SCOPE
에 없으면 GLOBAL_SCOPE
Binding에서 검색합니다.
ECMAScript의 “
global
“ property가(최상위 레벨의 “this”)GLOBAL_SCOPE
가 아닌ENGINE_SCOPE
에 있어 혼란이 있을 수 있습니다.더 많은 Bindings와 ScriptContext정보는 wiki를 참고하세요.
3. API and Language Extensions
Nashorn은 ECMAScript 표준 외에도 다양한 언어 및 API 확장 기능을 제공합니다. 대부분의 API 확장은 Rhino에 대한 호환성 때문에 발생합니다.
Rhino는 mozilla에서 개발한 Java로 구현된 JavaScript engine이다.
과거 Netscape에서 Java로 구현된 navigator를 구현하려는 시도를 한 적이 있는데, 이때 사용했던 JavaScript engine이 Rhino engine의 전신이된다.
Javagator라고 불리던 이 프로젝트는 JavaScript를 Java byte code로 컴파일하여 실행하기 때문에 당시에 있던 다른 브라우저보다 빠른 성능을 낼 수 있을것으로 기대했지만, JVM 자체의 성능 이슈와 다른 여러가지 상황때문에 중간에 중단되었지만, 일부 회사들의 지원으로 JavaScript framework은 분리되어 Rhino가 되었다.Rhino의 가장 큰 특징은 내부적으로 Reflection을 이용하여 JavaScript 코드에서 Java class를 그대로 가져다 쓸 수 있다는 것이다.
또한 Java구현체를 그대로 사용할 수 있기 때문에, JavaScript engine 중에서는 특이하게 multi thread support가 된다는 특징을 가진다.JVM이 꾸준히 성장하여 많은 성능 개선을 이루었지만, WebKit이 사용하는 JSC(JavaScript Core)나 Google이 개발한 v8 engine도 내부적으로 JavaScript를 compile하기 때문에 Rhino가 가지는 성능상의 이점은 없다. 사실상 v8이나 jsc보다 느리다.
성능상에 이점은 없지만, 반드시 Java를 사용해야 하거나 multi-core support가 필요한 일부 환경에서는 Rhino engine을 사용하는 경우가 있다. 하지만 이 중에 이름만 들어서 알만한 유명한 프로젝트는 없다.
Rhino를 사용하는 가장 유명한 구현체는 RingoJS로 보인다.
http://blog.seulgi.kim/2014/06/rhino-javascript-framework.html
제가 생각하기에 흥미롭고 유횽한 기능들 위주로 살펴 보도록 하겠습니다.
3.1. Print Function
Nashorn은 arguments
를 문자열로 변환 한 후 표준 출력(stdout
)으로 출력하는 기능을 제공합니다.
|
|
echo()
함수는print()
함수와 동일한 기능을 합니다.
Nashorn에는 브라우저에서 주로 로깅에 사용하는console
object를 가지고 있지 않습니다.
3.2. Stdin에서 읽기
stdin
에서 읽으려는 경우 readLine()
함수를 사용할 수 있습니다.
|
|
3.3. Files에서 읽기
stdin이 충분하지 않으면 readFully()
를 사용하여 전체 파일 내용을 변수로 읽을 수 있습니다.
|
|
3.4 문자열 Interpolation
${expression}
구문을 사용하여 문자열 리터럴에 표현식을 지정할 수 있습니다. 문자열 값은 ${expression}
표현식의 이름을 해당 변수의 값으로 대체하여 계산됩니다.
|
|
3.5 Back-quote 실행 표현식
Nashorn은 back quote
(역 따옴표) 문자열과 같은 유닉스 shell을 지원합니다. back quote로 묶인 문자열은 ‘exec’로 실행되고 리턴된 문자열을 프로그램에서 받을 수 있습니다.
test.js
이방법은 ES6의 새로운 역 따옴표 문자열 템플릿과 함께 사용하지 마십시오. 위에서 언급 한 문자열 Interpolation과 유사합니다!
3.6. Java Beans
Nashorn을 사용하면 Java bean의 getter
및 setter
로 작업하는 대신 Java bean에서 값을 가져 오거나 설정하는 데 간단히 속성 이름을 사용할 수 있습니다.
|
|
3.7. Function Literals
간단한 한 줄 함수의 경우, 반드시 중괄호를 사용할 필요는 없습니다 (또한 return 키워드도 생략할 수도 있습니다).
|
|
3.8. Binding Properties
두 개의 서로 다른 객체의 속성을 함께 바인딩할 수 있습니다.
|
|
3.9. Trimming Strings
trim()
뿐만 아니라 trimLeft()
, trimRight()
를 이용하여 문자열의 white space를 제거할 수 있습니다.
|
|
3.10. Whereis
만약 프로그램중에 현재의 위치를 알아야 할 경우 아래와 같이 사용할 수 있습니다.
|
|
3.11. Import Scopes
때로는 한 번에 많은 자바 패키지를 import 해야할 수도 있습니다. 그럴 경우 JavaImporter 클래스를 with 문과 함께 사용하여 import한 Java 패키지의 모든 클래스 파일을 with 문의 로컬 scope 내에서 액세스할 수 있습니다.
|
|
3.12. Packages
Pacakges
및 연관된 객체들이 script내에서 Java 패키지에 대한 액세스를 지원합니다. Packages
변수의 속성은 모두 Java, javax와 같은 최상위 Java 패키지입니다.
|
|
jjs에서 다음을 테스트 해보세요
|
|
3.13. Typed Arrays
Native JavaScript의 배열은 타입이 지정되지 않습니다. 하지만 Nashorn은 ECMAScript 2015 명세에 지정된 유형화된 배열을 구현합니다.
예를 들어 아래와 같이 int
형 타입을 가진 배열을 만들수 있습니다.
|
|
int[]
배열은, 실제의 Java int
배열과 같이 동작합니다. 또한 Nashorn은 정수가 아닌 값을 추가하려고 할 때 implicit 한 형변환을 수행합니다. 또한 가능한 경우 문자열은 자동으로 int
로 변환됩니다.
3.14. Collections and For-Each
자바 스크립트에서의 배열 처리는 때때로 짜증날때가 있습니다. 이제 Javascript의 배열의 사용 대신 모든 자바 컬렉션을 사용할 수 있습니다. 먼저 Java.type을 통해 Java 유형을 정의한 다음 필요에 따라 새 인스턴스를 작성하십시오.
|
|
컬렉션과 배열을 순회하는 Java의 foreach
문처럼 Nashorn
에는 for each
문이 있습니다.
다음은 HashMap
을 사용하는 다른 컬렉션의 for each
예제입니다.
|
|
3.15. Array 변환
java.util
, java.lang
과 같은 일부 패키지는 Java.type
또는 JavaImporter
를 사용하지 않고 직접 액세스할 수 있습니다.
|
|
아래 코드는 Java List를 Native JavaScript Array로 변환합니다.
|
|
그리고 그 반대의 경우는 아래와 같은 코드를 사용할 수 있습니다.
|
|
3.16. Lambdas and Streams
Java 8의 Lambda와 Streams을 Nashorn에서도 사용할 수 있습니다. 그리고
ECMAScript 5.1에는 Java 8 Lambda의 간단한 화살표 구문( ->
)이 없습니다. 하지만 Java 8 Lambda가 허용되는 곳에서는 함수 리터럴을 사용할 수 있습니다.
|
|
Java 8 Lambda 또는 SAM (single-abstract-method) 유형이 필요한 곳에서는 ECMAScript 함수를 인수로 전달할 수 있습니다!
3.16.1. 모든 람다식은 JavaScript Function 입니다.
람다 형식의 인스턴스인 모든 Java 객체는 JavaScript Function처럼 취급 될 수 있습니다.
|
|
3.17. Extending Classes
Java 타입은 Java.extend 함수를 사용하여 간단히 확장할 수 있습니다.
|
|
위 코드에서 보듯이 Nashorn에서는 멀티 스레드 코드도 가능합니다.
3.18. Calling Super
ECMAScript에 Java의 super
에 해당하는 키워드가 없기 때문에 JavaScript에서는 오버라이드된 멤버를 액세스하는 것은 어렵습니다. 하지만 Nashorn에서는 가능 합니다.
먼저 Java 코드에서 super class를 정의합니다.
|
|
다음으로 JavaScript로부터 SuperRunner를 오버라이드(override) 합니다.
새로운 Runner 인스턴스를 생성할때 확장된 Nashorn 구문을 사용하는데 주의하세요.
오버라이드 멤버 구문은 Java의 익명 객체를 이용하여 정의 합니다.
|
|
Java.super 확장을 활용하여 재정의된 메소드 SuperRunner.run ()을 호출합니다.
3.19. Loading Scripts
JavaScript에서 추가 스크립트 파일을 실행 하는 것은 매우 쉽습니다. load 함수를 사용하여 로컬 또는 원격 스크립트를 로드할 수 있습니다.
아래의 예제는 moment.js와 Underscore.js를 로드해서 사용하는 예제입니다.
|
|
|
|
외부 스크립트는 동일한 JavaScript context
에서 evaluate 되므로 moment, Underscore를 직접 액세스할 수 있습니다.
3.19.1. Load in new Global Context
외부 파일을 로드할 때 로드된 코드에 자신의 코드에 있는 동일한 변수 이름이 사용될 수 있습니다. 이를 방지하기 위해 파일을 새로운 global context
으로 로드할 수 있습니다.
|
|
위와 같이 로드된 스크립트는 Nashorn glogal context
(현재 engine context
가 아님)에서만 사용할 수 있습니다.
3.20. Error Object
Nashorn은 ECMAScript의 표준 Error 객체에 몇 가지 흥미로운 속성을 추가 하여 확장했습니다.
3.21. Scripting Mode Extension Objects
Nashorn에는 -scripting
모드로 실행 되었을때 사용할 수 있는 몇몇 global 객체가 정의되어 있습니다.
대부분은 바로 알수 있는 부분이라 아래의 소스 코드를 참고하길 바랍니다.
3.21.1. $ARG
|
|
arguments
3.21.2. $ENV
|
|
3.21.3. $EXEC
Shell 명령어 수행을 위한 프로세스 실행
|
|
3.21.4. $OUT
$OUT
변수는 가장 최근에 $EXEC
로 실행된 프로세스의 stdout
출력이 저장됩니다.
|
|
3.21.5. $ERR
$ERR
변수는 가장 최근에 $EXEC
로 실행된 프로세스의 stderr
출력이 저장됩니다.
3.21.6. $EXIT
$EXIT
변수는 $EXEC
로 실행된 프로세스의 종료 코드가 저장됩니다.
3.21.7. $OPTIONS
이 속성은 Nashorn CLI의 실행 옵션을 제공합니다.
|
|
Nashorn이 구현한 다양한 언어 및 API 확장에 대한 설명은 Wiki 페이지에서 확인할 수 있습니다.
4. Working with Package Managers & Repositories
Nashorn
으로 작업할 때, 처음부터 모든 기능을 개발하고 싶지는 않을 것입니다. 다행이도 Maven
의 의존성 라이브러리를 사용하는 것처럼 이미 필요한 기능을 제공하는 Nashorn
라이브러리를 사용할 수 있습니다. 게다가 Java, JavaScript와 완벽히 호환되는 다양한 Repository와 Package Manager를 선택할 수도 있습니다.
4.1. NPM
Node Package Manager - https://npmjs.org
npm은 Node.js의 Package Manager이고 개발자들이 JavaScript 애플리케이션에서 파일, 메타데이터, 의존성을 쉽게 관리하도록 만들어졌습니다. 2009년 오픈 소스 프로젝트로 시작된 npm은 개발자들이 인터넷을 통한 서비스로 오픈 소스 코드를 관리할 수 있도록 패키지 저장소 기능을 제공합니다. (https://nodejs.github.io/nodejs-ko/articles/2015/06/17/npm-is-massive/)
- NPM은 JavaScript의 server-side 프로그램인 Node.js 패키지 관리 프로그램입니다.
- NPM Repository는 오픈 소스코드의 공개 컬랙션입니다.
- 또한 NPM은 Command line 클라이언트 입니다.
Nashorn
내에서 NPM 패키지를 사용할 수 있습니다!. 하지만 …
4.1.1. JVM-NPM
대부분의 많은 JavaScript 패키지는 CommonJS
의 require()
문 (Java에서 import
와 같은)을 사용합니다. 하지만 불행이도 Nashorn
은 JavaScript 엔진일 뿐이고 Package Manager 및 dependency loading 매커니즘을 지원하지 않습니다.
하지만 다행이도 npm-jvm
(JVM 용 NPM 호환 CommonJS
모듈 로더) 프로젝트가 있습니다. 이 모듈은 Nashorn
script context에서 require()
함수를 사용할 수 있도록 합니다. Nashorn
script context에서 CommonJS
에 의존성이 있는 패키지를 사용하려 할 때 require()
함수를 사용하여 다른 모듈을 로드할 수 있습니다.
Usage
Nashorn이 제공하는 global load()
함수를 사용하여 jvm-npm.js를 global execution context에 로드합니다. 그런 다음 require()
를 사용하여 원하는 모듈을 로드할 수 있습니다.
|
|
또는 비슷한 역할을 하는 commonjs-modules-javax-script
프로젝트도 있습니다.
4.1.2. Polyfill.js
NPM에 등록된 많은 라이브러리는 Node.js 또는 브라우저 공통 API를 사용합니다. Nashorn은 Node.js도 브라우저도 아니기 때문에 라이브러리에서 사용하는 Node.js 또는 브라우저의 공통 API를 정의 하여야 합니다. 이러한 부분을 보통 polyfill이라 부르고 사용할 수없는 환경에서 필요한 기능을 제공하기 위해 사용합니다.
nashorn-polyfill.js
(1) Node.js에 있는 global
variable을 위해 제공 (global context)
(2) 브라우저에 있는 window
variable을 위해 제공 (global context)
(3) 몇몇 라이브러리에서 사용하는 Node.js의 process.env
variable을 위해 제공
(4) Nashorn에는 console
이 없기 때문에 가장 많이 사용되는 콘솔 출력 함수들을 print 함수에 할당합니다.
4.1.3. Native API access
Nashorn은 Native API (C/C++)를 액세스할 수 없습니다. 그래서 Native API를 사용하는 NPM 패키지는 Nashorn에서 사용할 수 없습니다.
4.2. Maven
Java 프로젝트 내에서 Nashorn을 사용할 때 대부분의 경우 Maven을 저장소 및 패키지 관리자로 사용합니다. 몇가지 plugin과 helper를 이용하면 문제 없이 사용할 수 있습니다.
4.2.1. Maven and NPM, Grunt, Gulp, etc.
Node.js ecosystem, NPM 그리고 프론트 엔드 빌드 도구인 Grunt, Gulp, Webpack등과 같이 사용하고 싶다면 아래의 편리한 Maven plugin을 사용할 수 있습니다.
https://github.com/eirslett/frontend-maven-plugin/ (이 plugin에 대한 자세한 설명과 문서는 Github 페이지를 참고하세요.)
4.2.2. WebJars
가장 많이 사용되고 인기 있는 NPM 패키지는 WebJars
에서 제공하고 있습니다. WebJars
를 사용하면 NPM 패키지를 프로젝트에서 Maven dependency를 이용하여 사용할 수 있습니다.
아래는 Moment.js
를 Maven에서 사용하는 예제입니다.
|
|
만약 사용할수 있는 NPM 패키지가 Webjar에 없다면 웹 페이지에 있는 자세히 설명된 문서를 이용하여 쉽게 추가할 수 있습니다.
4.2.3. Nasven
Nasven.js는 Maven을 이용하여 아티팩트의 dependency를 관리하는 JavaScript 애플리케이션(서버, 데스크탑 및 쉘 스크립트)의 런타임입니다.
개발자는 순수한 서버 측 응용 프로그램, 쉘 스크립트 또는 JavaFX 데스크탑 응용 프로그램을 만들 수 있으며 Maven dependency는 클래스 경로에 자동으로 다운로드되고 구성됩니다.
Nasven = Nashorn + Maven.
|
|
예제
5. Isomorphic JavaScript
5.1 Isomorphic의 뜻, 그리고 무엇을 할 수 있고 왜 사용해야 하는가?
Isomorphic
은 그리스어의 “isos”에서 “equal”의 의미를 , “morph”는 “shape”의 의미가 유래 되었습니다. 그래서, 동일하거나 동등한 형태의 뜻을 나타냅니다.
Isomorphism은 동일한 엔티티
(1)에 대해 두개의 서로 다른 context
(2) 에서 같은 결과
(3)를 내야 한다고 설명합니다.(http://isomorphic.net/javascript)
- code
- client and server
- result, html, DOM, etc.
우리의 경우에는 “우리가 클라이언트와 서버에서 같은 JavaScript 코드를 사용한다면 우리는 같은 결과/html을 얻어야한다.”입니다.
5.1.1. Isomorphic code
Isomorphic 코드는 클라이언트와 서버 측에서 동일한 로직을 공유할 수 있으므로 DRY 원칙 (Don’t Repeat Yourself)을 활용한 코드 작성이 가능합니다. 애플리케이션 로직은 한번 개발되고 하나의 코드베이스에서만 유지 하면됩니다. 만약 오류가 발생하는 경우 코드의 수정은 한곳에서만 진행하면 됩니다. 그리고 Isomorphic 코드로 인해 개발자가 하나의 기술에만 집중할 수 있으므로 여러 프로그래밍 언어에 대한 전문가가 될 필요는 없습니다.
Example
아래의 예제는 브라우저에서 사용하기 위해 JavaScript로 개발 한 패스워드 검증 코드입니다.
|
|
위 코드는 잘 작동하지만 만약 사용자가 브라우저에서 JavaScript를 사용하지 못하도록하고, 잘못된 암호를 서버에 전달하는 경우를 대비해, 서버(Java)에서 사용되는 프로그래밍 언어로 동일한 로직을 다시 개발해야합니다.
이것은 추가적인 노력이며, 구현할때 오류로 이어질 수 있고, 최근 변경된 로직에 대한 부분을 잊어 버리고 미구현할 수 있습니다.
Java 코드에서 JavaScript 코드 로직을 다시 사용하지 않을 이유가 있을까요? 아래 예제가 그 해법을 제시합니다.
|
|
5.1.2. SPAs
현재 대부분의 SPA(Single Page Web Application)는 HTML 스켈레톤과 UI 렌더링, transition 관리, path routing, 네비게이션, 로직등을 하나 이상의 JavaScript 라이브러리에서 처리 합니다.
index.html
이러한 응용 프로그램은 대단히 훌륭하지만 몇 가지 단점이 있습니다.
단점
- UX/Performance - 전체 스크립트 코드가 클라이언트(브라우저)에서 로드, 평가 및 실행되기 전까지 사용자는 아무 것도 볼 수 없습니다.
- 인터넷 연결 속도가 빠름에도 불구하고 Long wait time
- 사용자가 떠날 가능성이 있음 (3초 룰)
- 느린 인터넷 연결을 생각해 보십시오!
- Legacy Browser(아직 많이 사용하고 있습니다.)에서는 새로운 자바 스크립트를 실행시킬수 없습니다.
SEO - 컨텐츠가 없기 때문에 검색엔진에서 여러분의 웹사이트를 인덱스할 수 없습니다.
- Google이 웹 사이트에서 JavaScript를 실행(evaluate)할 수 있다고 해도 여전히 오류가 발생하기 쉽습니다.
위와 같은 이유로 웹 페이지를 서버에 렌더링하는 것이 더 좋을수 있습니다.
장점
하지만 이건 어떨까요?
- UX - 멋진 화면 전환 효과 (다른 동종의 사이트들과 경쟁할 수 있어야 합니다.)
- Performance - 페이지의 일부분만 빠르게 렌더링
- Performance - 전체 HTML 대신 필요한 데이터만 전송
이러한 장점들은 클라이언트에서 프로그램을 실행하고 페이지를 렌더링할 때만 얻을 수 있습니다!
해결방법
- 사용자가 처음 URL을 요청합니다.
- 서버가 해당 URL의 콘텐츠를 가져옵니다.
- 서버에서 콘텐츠를 응답 형태로 렌더링합니다.
- 사용자는 콘텐츠를 봅니다.
- 그 동안에 클라이언트가 초기화됩니다.
- 사용자가 다른 URL로 이동합니다.
- 클라이언트가 해당 URL의 콘텐츠를 가져옵니다.
- 클라이언트가 DOM에 내용을 렌더링합니다.
사용자가 앱 또는 앱 경로에 대한 초기 요청할 때 페이지를 서버에서 렌더링하여 클라이언트에게 전송합니다.
그 순간부터 클라이언트는 콘텐트를 제어하고 콘텐트 렌더링을 계속할 수 있습니다.
이렇게하면 사용자가 초기 서버 측 렌더링 페이지를 사용하는 동안 클라이언트를 초기화하는 시간이 줄어듭니다.
그리고 검색 엔진에서 사이트의 인덱스를 생성하는 경우 이미 렌더링된 콘텐츠의 유효한 HTML을 서버에서 가져올수 있습니다.
그리고 기존 브라우저는 클라이언트가 페이지를 렌더링할 수 없는 경우에도 사이트를 사용할 수 있습니다.
5.2. React.js
React.js는 Facebook에서 개발 한 사용자 인터페이스를 구현한 JavaScript 라이브러리입니다. 앱을 빌드하기 위한 전체 스택 클라이언트 프레임 워크가 아닙니다. MVC 또는 MVVM에서 React.js는 V(iew) 부분을 컴포넌트로 만들기 위한 라이브러리입니다.
React.js는 컴포넌트 기반 가상 DOM을 가지고 있습니다. 이 가상 DOM과 비교후 다른 부분을 적용하여 깜박임없이 페이지 전환 또는 콘텐츠 업데이트가 가능합니다. 또한 React는 템플릿의 서버 측 렌더링을 지원합니다.
5.2.1. Flux
React를 이용하여 full-stack 애플리케이션을 구현하는 방법중에 Flux
라는 아키텍처 방식이 있습니다.
이 아키텍처에는 직접 접근으로 변경할 수없는 Immutable Entity
및 collection
이 있습니다. 응용 프로그램의 상태는 Store
에 저장되고, 이 Store
는 템플릿을 렌더링하기위한 데이터들이 저장됩니다. 또한 Store
는 Action
의 Event
(데이터 포함)를 받은
Dispatcher만 수정할 수 있습니다. Action만이 Store가 외부 세계와 소통할 수있는 유일한 방법입니다.
현재 가장 널리 사용되는 Flux 구현 라이브러리는 Redux가 있고 다른 많은 라이브러리도 있습니다.
5.2.2. JSX
React.js는 JavaScript의 새로은 변형인 JSX를 HTML 요소와 혼합하여 많이 사용합니다. 아마도 처음에는 조금 어색할수 있지만 더 많이 사용하면 편리하다는 것을 느끼게 됩니다.
app.jsx
(위의 코드를 볼때 몇몇분들은 JSP가 생각날 수도 있습니다….)
JSX는 Babel.js를 사용하여 실행 가능한 JavaScript (ES5)로 변환 됩니다. (이전에는 Facebook의 라이브러리 인 JSXTransformer에서이 작업을 수행했지만 더 강력한 Babel로 바뀌었습니다.)
app.js
런타임시 위의 JavaScript 코드는 적절한 HTML로 렌더링됩니다.
app.html
React는 데이터 변경시 data-reactid 속성을 통해 변경해야하는 (가상) DOM 부분을 찾을 수 있습니다.
React.js, JSX 및 Flux에 대한 자세한 내용은 해당 웹 사이트를 참조하십시오!
5.3. Isomorphic App을 위한 Spring Boot MVC
서버 측 렌더링을 위해 Spring (Boot) MVC 및 React.js를 사용하는 예제가 몇 가지 있습니다.
Spring MVC로 Isomorphic 응용 프로그램을 만드는 데 관심이 있다면이 위의 링크들을 참조하십시오.
5.4. Isomorphic App을 위한 Java EE 8 MVC 1.0
Java EE 8과 그 참조 구현인 Ozark에서 Action-based Web-Framework MVC 1.0을 새로 만들기 위해 React.js의 튜토리얼을 참고하여 ViewEngine 예제를 작성했습니다.
위의 두 저장소에서 가져온 아래 간단한 코드 조각은 JavaScript/Java EE 애플리케이션이 어떻게 Isomorphic으로 구현이 되는지 보여줍니다.
React 기반 ViewEngine을 사용하여 응용 프로그램을 만들때 위에서 언급한 저장소의 원래 코드를 사용해야합니다. 원래 코드들은 아래의 단순화된 예제보다 훨씬 더 유연하고 강력합니다!
ReactController는 새로운 @Controller
어노테이션을 사용한 표준 MVC 컨트롤러입니다.
ReactController.java
- MVC 컨트롤러 어노테이션
- Map 형식의 MVC 내부 Model Entity
- 데이터를 조회하거나 저장하는 서비스
- Book list를 가져와서 Java 오브젝트를 Model에 넣습니다.
- ReactViewEngine이 사용할
react:
접두어가 있는 템플릿의 Path를 반환합니다.
아래 예제가 React.js와 상호 작용하는 실제 ViewEngine 구현 클래스 입니다.
ReactViewEngine.java
React
클래스는 React.js JavaScript 코드와 상호 작용합니다. (자세한 내용은 아래 클래스를 참조하십시오.)supports()
메서드는 이ReactViewEngine
클래스가 컨트롤러의 리턴 문자열에 적합한 ViewEngine으로 사용되어도 되는지 여부를 판단 합니다.processView()
메서드는 실제로 View를 처리하기 위한 일을 합니다. 자세한 내용은 인라인 주석을 참조하십시오.
아래 React
클래스는 JavaScript React.js와 상호 작용하는 클래스입니다.
React.java
- 필요한 JavaScript 라이브러리들과 함께 새로운
ThreadLocal<ScriptEngine>
을 초기화 합니다. 왜냐하면 React.js는 Thread Safe하지 않기 때문에 각 요청에서 전용 ScriptEngine을 사용해야 합니다. - JSX/JS 코드의 renderServer 함수를 호출하고 결과를 문자열로 반환합니다 (아래 코드 참조).
- 중복 코드 제거를 위한 메서드
아래 코드는 original JSX 코드입니다. (Nashorn에 로드하기 전에 JS로 렌더링 됨, Babel.js 라이브러리를 빌드 도중 또는 런타임에 Nashorn ScriptEngine에 로드할 수 있지만 로드 시간이 더 길어질 수 있음).
bookBox.jsx
|
|
- 이전 코드 생략.
- 이 함수는 클라이언트에 의해 호출되며 응용 프로그램을 초기화하고 컨텐츠를 렌더링합니다.
- 이 함수는 서버 (위
React.java
참조)에 의해 호출되어 컨텐츠를 렌더링합니다. Component는 (1)에 있는 생략된 코드입니다. 실제 isomorphic 코드입니다.
추가 정보가 포함된 HTML skeleton이 서버에서 렌더링되고 클라이언트에 전송됩니다.
react.jsp
- WebJars를 이용한 JavaScript 라이브러리 (Maven/Gradle 빌드 파일에서 dependency 설정)
- Client 또는 Server에서 렌더링된 내용을 넣을
div
- 실제 응용 프로그램 스크립트 (bookBox.jsx 참고)
- 응용 프로그램이 클라이언트 측에서 실행 또는 초기화 될 때 호출 되는 함수입니다 (bookBox.js 참고)
ReactController
에서 ReactViewEngine
을 호출하지 않고 애플리케이션이 시작된 경우 (예를 들어 “react.jsp
“만 반환하면 표준 JSP ViewEngine을 사용합니다.), 서버에서 수신 받은 초기 데이터를 보면 <div id = "content"/>
엘리먼트에는 눈으로 확인할 수 있는 HTML 코드가 포함되어 있지 않습니다. 하지만 renderClient()를 클라이언트에서 호출하기 때문에 브라우저에서 렌더링되고 컨텐츠가 표시됩니다.
프로그램에서 ReactViewEngine을 사용하면 서버에서 받은 초기 코드의 <div id = "content"/>
엘리먼트에는 렌더링된 컨텐츠가 표시 됩니다.
클라이언트의 renderClient()
함수도 실행이 되지만 (가상) DOM에는 변경 사항이 없으므로 페이지가 그대로 유지되고, DOM이 다시 렌더링되지 않아 깜박임 현상이 없어집니다.
6.JavaFX with JavaScript
http://www.n-k.de/riding-the-nashorn/#_javafx_with_javascript
JavaFx는 한국에서 별로 인기가 없어서 따로 번역을 하지 않았습니다.
자세한 설명은 원문을 참고하세요.
7. Nashorn Script 테스트와 디버깅 하기
7.1. Testing
Nashorn 스크립트의 테스트는 아직 사용해 보지 않아서 어려워 보일수 있지만 그렇지 않습니다.
7.1.1. Function(al) testing with JUnit
모든 JavaScript 함수에는 반환 값이 있으므로 (모든 JavaScript 함수, 명시 적 반환 값이없는 함수조차도 undefined를 반환합니다) 이러한 값을 지정할 수 있습니다.
Java 메서드를 개발하는 것처럼 JavaScript 파일을 빌드, 테스트 가능한 atomic 함수로 작업하면됩니다.
그런 다음 테스트에서 JavaScript 함수를 호출하고 결과를 assert 선언할 수 있습니다.
다음 JavaScript 코드가 있다고 가정 해 보겠습니다.
calculator.js
이제 다음 표준 JUnit 테스트를 사용하여 함수를 테스트할 수 있습니다
CalculatorTest.java
7.1.2. Mocking JavaScript functions & using Spock
Nashorn 스크립트를 테스트하는 훨씬 더 좋은 (더 강력한) 방법은 JUnit 대신 Spock을 사용하는 것입니다. Spock은 동적언어 Groovy 코드를 기반으로 작성되었습니다. 그렇기 때문에 Groovy로 만든 객체와 함수를 Nashorn과 테스트 하기 쉽습니다
Spock을 사용하면 직접 제어할 수 없거나 Nashorn에서 사용할 수없는 기능에 대해 mock function을 사용하기가 매우 쉽습니다. 이런 제어할수 없는 기능들 중에는 테스트 중에는 실행하고 싶지 않은 콜백 함수 (JavaScript에서 널리 사용됨), alert() 함수 등이 있을수 있습니다.
Callback.groovy
- 이 함수는 테스트 호출시 inject된 다른 콜백 함수 (java.util.function.Function 유형)를 가져옵니다.
- 이 함수에서 콜백 함수는 mock 함수로 대체 되고, mock 함수가 “John”값으로 한 번 호출된 이후에 테스트됩니다.
- Nashorn에는 사용할수 있는 alert() 함수가 없기 때문에 실제 함수가 호출되기 전에 Nashorn context에 추가해야합니다.
Spock과 Nashorn의 더 많은 예제는 GitHub 저장소 dasniko/nashorn-spock-jasmine에서 찾을 수 있습니다.
7.2. Debugging
Nashorn JavaScript의 디버깅은 다음과 같은 주요 IDE에서 지원됩니다.
- JetBrains IntelliJ IDEA (13.1 이상)
- Oracle Netbeans (version 8 이상)
- Eclipse
- Nodeclipse로 가능 합니다.