|
|
난이도 : 초급
Tom McQueeney, Lead Technical Consultant, Idea Integration
2007 년 10 월 16 일
자바 애플리케이션에서 사용하기 위해 동적인 언어를 자바™ 바이트코드로 컴파일 할 필요가 없습니다. 수 십 개의 스크립팅 언어들이 Java Platform, Standard Edition 6 (Java SE)(Java SE 5와 백워드 호환)에 추가된 스크립팅 패키지를 사용하여 단순하고, 통합된 방식으로 자바 코드에서 런타임 시 호출될 수 있습니다. 두 편의 기술자료로 구성된 본 시리즈의 Part 1에서는 자바 스크립팅 API의 기능을 소개합니다. 간단한 Hello World 애플리케이션을 사용하여 자바 코드가 스크립팅 코드를 실행하는 방법과 스크립트가 자바 코드를 실행하는 방법을 설명합니다. Part 2에서는 자바 스크립팅 API의 힘을 보다 심층적으로 설명합니다.
자바 개발자들이라면, 자바가 모든 태스크에 최상의 언어가 아니라는 것쯤은 알고 있다. 올해 릴리스 된 1.0 버전의 JRuby와 Groovy는 자바 애플리케이션에 동적인 언어를 추가하는데 주력했다. Groovy, JRuby, Rhino, Jython, 및 기타 오픈 소스 프로젝트들은 소위 말하는 스크립팅 언어로 코드를 작성할 수 있고 JVM 내에서 이를 실행할 수 있다. (참고자료). 그러나, 이러한 언어들과 자바 코드를 통합하려면 각 인터프리터의 고유 API와 기능들을 배워야 한다.
Java SE 6에 추가된 javax.script 패키지로 동적인 언어를 더욱 쉽게 통합할 수 있다. 간단한 방식으로 인터페이스와 구체적인 클래스를 사용하여 수 십 개의 스크립팅 언어들을 호출할 수 있다. 하지만, 자바 스크립팅 API는 애플리케이션의 부분들을 쉽게 스크립팅 하는 데만 쓰이는 것은 아니다. 스크립팅 패키지로 외부 스크립트를 런타임 시 읽고 호출할 수 있으며, 그러한 스크립트를 동적으로 수정하여 구동 중인 애플리케이션의 작동을 수정할 수 있다.
이 글에서는 Hello World 스타일 애플리케이션을 사용하여 자바 스크립팅 API의 기능과 핵심 클래스들을 소개한다. Part 2에서는 보다 현실적인 샘플 애플리케이션을 통해 스크립팅 API의 힘을 보여 줄 것이다. 이 애플리케이션은 스크립팅 API를 사용하여 규칙들이 Groovy, xxJavaScript, Ruby로 작성된 외부 스크립트로서 인코딩 되는 동적 규칙 엔진을 구현한다. The rules decide whether applicants for a home loan qualify for particular mortgage products. 이러한 규칙을 자바 스크립팅 API로 외부화 하면 규칙들은 새로운 모기지 제품들이 런타임 시 추가될 수 있도록 수정한다.
자바 스크립팅 API
|
스크립팅 대 동성(dynamic)
스크립팅(scripting)이란 용어는 독립된 컴파일 단계 없이 인터프리터 쉘에서 실행되는 언어를 일컫는다. 동적(dynamic)이란 용어는 변수의 유형이나 객체의 작동을 결정하고 클로저(closure)와 continuation 같은 기능을 포함시키기 위해 런타임 될 때까지 기다리는 언어를 일컫는다. 여러 범용 프로그래밍 언어들이 두 용어 모두 해당한다. 이 글에서는, 자바 스크립팅 API에 초점을 맞추기 때문에 스크립팅 언어라는 용어를 사용하는 것이 더 맞다. | |
스크립팅 패키지가 2006년 12월에 자바에 추가되어 스크립팅 언어들을 자바 애플리케이션에 통합하는 일관된 방식을 제공한다. 언어 개발자에게, 이 패키지는 자신들이 개발한 언어들이 자바 애플리케이션에서 동적으로 호출될 수 있도록 하는 필수 글루 코드(glue code)를 작성하는 방법을 제공한다. 자바 개발자에게, 이 스크립팅 패키지는 많은 언어들로 작성된 스크립트들이 공통의 API를 사용하여 호출될 수 있도록 하는 클래스와 인터페이스 세트를 제공한다. 다른 언어들이 일관된 인터페이스를 사용하여 자바 플랫폼으로 통합될 수 있다는 점에서, 이 스크립팅 패키지는 Java Database Connectivity (JDBC) 패키지와 비슷하다.
이전에는, 자바 코드에서 스크립팅 언어를 동적으로 호출할 때에는 각 언어 배포판에서 제공된 고유 클래스를 사용하거나 Apache의 Jakarta Bean Scripting Framework (BSF)를 사용했다. BSF는 하나의 API 뒤에 몇몇 스크립팅 언어들을 통합한다. (참고자료) AppleScript, Groovy, xxJavaScript, Jelly, PHP, Python, Ruby, Velocity를 포함하여 20여 개가 넘는 언어들이 Java SE 6 scripting API를 사용하여 자바 코드로 통합될 수 있는데, 이것은 BSF에 의존하고 있다.
이 스크립팅 API는 자바 애플리케이션과 외부 스크립트 간 투웨이(two-way) 가시성을 제공한다. 자바 코드는 외부 스크립트를 호출할 뿐만 아니라, 그러한 스크립트가 선택된 자바 객체들에 액세스 할 수 있도록 한다. 외부 Ruby 스크립트의 경우, 자바 객체에 메소드를 호출하고 이것의 프로퍼티에 액세스 하면서, 그러한 스크립트들이 개발 시 예상하지 못한 실행 애플리케이션에 작동을 추가할 수 있도록 한다.
외부 스크립트를 호출하는 것은 런타임 애플리케이션 향상, 설정, 모니터링, 기타 런타임 조작(애플리케이션을 중지하지 않고 비즈니스 규칙 바꾸기)을 위해 사용될 수 있다. 스크립팅 패키지는 다음과 같이 사용될 수 있다.
- 완벽한 규칙 엔진에 의존하지 않고, 자바 언어보다 단순한 언어로 비즈니스 규칙을 작성한다.
- 플러그인 아키텍처를 생성하여 사용자들이 즉석에서 애플리케이션을 커스터마이징 할 수 있도록 한다.
- 기존 스크립트를 텍스트 파일들을 처리하거나 변형하는 스크립트 같은 자바 애플리케이션에 통합시킨다.
- 프로퍼티 파일 대신 완전한 프로그래밍 언어를 사용하여 애플리케이션의 런타임 작동을 외부에서 설정한다.
- 도메인 스팩의 언어를 자바 애플리케이션에 추가한다.
- 스크립팅 언어를 사용하면서, 자바 애플리케이션을 프로토타이핑 한다.
- 스크립팅 언어로 애플리케이션 테스트 코드를 작성한다.
Hello, scripting world
HelloScriptingWorld 클래스는(다운로드) 자바 스크립팅 패키지의 핵심 기능들을 대표한다. 샘플 스크립팅 언어로서 xxJavaScript의 하드 코딩 스니펫을 사용한다. 이 클래스의 main() 메소드(Listing 1)는 xxJavaScript 스크립트 엔진을 만들어서, 스크립팅 패키지의 기능을 강조하는 다섯 개(Listing 참조)의 메소드를 호출한다.
Listing 1. HelloScriptingWorld 메인 메소드
public static void main(String[] args) throws ScriptException, NoSuchMethodException {
ScriptEngineManager scriptEngineMgr = new ScriptEngineManager();
ScriptEngine jsEngine = scriptEngineMgr.getEngineByName("xxJavaScript");
if (jsEngine == null) {
System.err.println("No script engine found for xxJavaScript");
System.exit(1);
}
System.out.println("Calling invokeHelloScript...");
invokeHelloScript(jsEngine);
System.out.println("\nCalling defineScriptFunction...");
defineScriptFunction(jsEngine);
System.out.println("\nCalling invokeScriptFunctionFromEngine...");
invokeScriptFunctionFromEngine(jsEngine);
System.out.println("\nCalling invokeScriptFunctionFromJava...");
invokeScriptFunctionFromJava(jsEngine);
System.out.println("\nCalling invokeJavaFromScriptFunction...");
invokeJavaFromScriptFunction(jsEngine);
}
|
main() 메소드의 주요 기능은 javax.script.ScriptEngine 의 인스턴스를 얻는 것이다. (Listing 1의 첫 두 개의 문). 스크립트 엔진은 특정 언어로 된 스크립트를 로딩하고 실행한다. 이것은 자바 스크립팅 패키지에서 가장 자주 사용되는 중요한 클래스이다. 스크립트 엔진을 javax.script.ScriptEngineManager (첫 번째 문)에서 얻는다. 전형적인 프로그램은 많은 스크립팅 언어들이 사용되지 않는 한, 스크립트 엔진의 한 인스턴스만 얻는다.
ScriptEngineManager 클래스
ScriptEngineManager 는 여러분이 규칙적으로 사용하게 될 스크립팅 언어의 유일한 구체적 클래스일 것이다. 나머지는 거의 인터페이스이다. 직접 인스턴스로 만들(또는 Spring Framework 같은 의존성 투입 메커니즘을 통해 간접적으로) 스크립팅 패키지에서 유일한 클래스일 것이다. ScriptEngineManager 는 다음 세 가지 중 한 가지 방법으로 스크립트 엔진을 리턴할 수 있다.
xxJavaScript 엔진을 요청하는 Listing 1에서처럼 엔진이나 언어 이름을 사용.
- Ruby 스크립트용 .rb 같이, 그 언어의 스크립트에 공통으로 사용되는 파일 확장 사용.
- 스크립트 엔진이 처리 방법을 알고 있음을 선언하는 MIME 유형 사용.
|
왜 xxJavaScript 예제인가?
Hello World 예제에서 xxJavaScript를 사용하는 이유는, 코드가 이해하기 쉬워서도 있지만, Sun Microsystems 와 BEA Systems에서 제공되는 Java 6 런타임 환경들에 Mozilla Rhino 오픈 소스 xxJavaScript 구현에 기반한 xxJavaScript 인터프리터가 번들 되어 있기 때문이다. xxJavaScript를 사용하면, 스크립트-언어 JAR 파일을 classpath에 추가할 필요가 없다. | |
ScriptEngineManager 는 스크립트 엔진들을 간접적으로 찾고 생성한다. 스크립트-엔진 매니저들이 인스턴스화 되면, 이들은 Java 6 에 추가된 서비스-발견 메커니즘을 사용하여 등록된 모든 javax.script.ScriptEngineFactory 구현을 classpath에서 찾는다. 이러한 팩토리 클래스들은 자바 스크립팅 API 구현에 압축되어 있다. 이러한 팩토리 클래스들을 직접 다룰 필요가 없다.
ScriptEngineManager 가 모든 스크립트-엔진 팩토리 클래스를 찾으면, 하나씩 쿼리하여 이것이 요청된 유형의 스크립트 엔진(Listing 1의 경우, xxJavaScript 엔진)을 생성할 수 있는지 여부를 파악한다. 팩토리가 원하는 언어를 위한 스크립트 엔진을 생성할 수 있다는 것을 알려주면, 매니저는 팩토리에게 엔진을 생성할 것을 요청하면, 이것은 콜러에게 리턴된다. 요청된 언어에 대한 어떤 팩토리도 찾지 못하면 매니저는 null 을 리턴하는데, Listing 1의 코드는 null 리턴 값을 체크함으로써 보호한다.
ScriptEngine 인터페이스
필자가 언급했던 것처럼, 코드는 ScriptEngine 인스턴스를 사용하여 스크립트를 실행한다. 스크립트 엔진은 스크립팅 코드와 기반 언어 인터프리터 또는 코드를 실행하는 컴파일러 사이의 중개자로서 작동한다. 코드를 실행하기 위해 각 인터프리터가 사용하는 클래스가 어떤 것인지 알 필요가 없다. 예를 들어, JRuby용 스크립트 엔진은 코드를 JRuby의 org.jruby.Ruby 클래스로 전달하여 스크립트를 중간 폼으로 컴파일 하고, 이것을 다시 호출하여 스크립트를 계산하고 리턴 값을 처리한다. 스크립트-엔진 구현은 인터프리터가 클래스 정의, 애플리케이션 객체, 인풋/아웃풋 스트림을 자바 코드와 공유하는 방법을 포함하여 상세를 숨긴다.
그림 1은 애플리케이션, 자바 스크립팅 API, ScriptEngine 구현, 스크립팅-언어 인터프리터 사이의 일반적인 관계를 묘사한 것이다. 애플리케이션은 스크립팅 API에만 의존하는데, 이는 ScriptEngineManager 클래스와 ScriptEngine 인터페이스를 제공한다. ScriptEngine 구현 컴포넌트는 특정 스크립팅 언어 인터프리터를 사용하는 스팩을 핸들한다. 그림 1: 스크립팅 API 컴포넌트 관계
script-engine 구현과 언어 인터프리터용 필수 JAR 파일들을 어디에서 구하는지 궁금할 것이다. script-engine 구현을 찾을 수 있는 최상의 장소는 java.net에서 호스팅 되는 오픈 소스 Scripting 프로젝트이다. (참고자료) 많은 언어들을 위한 script-engine 구현들을 찾을 수 있고, 여러 곳에 호스팅 된 script-engine 구현들에 대한 링크도 있다. Scripting 프로젝트는 또한 이것이 지원하는 스크립팅 언어용 인터프리터를 다운로드 할 수 있는 링크도 제공한다.
Listing 1에서, main() 메소드는 ScriptEngine 을 메소드의 xxJavaScript 코드를 계산할 때 사용하기 위해 각 메소드로 전달한다. 첫 번째 메소드는 Listing 2에 나타나 있다. invokeHelloScript() 메소드는 스크립트 엔진의 eval 메소드를 호출하여 xxJavaScript 코드의 스트링을 계산 및 실행한다. ScriptEngine 인터페이스는 계산할 스크립트를 스트링 또는 java.io.Reader 객체로서 받아들이는 여섯 개의 오버 로딩된 eval() 메소드를 정의한다. 이는 파일 같은 외부 소스들에서 스크립트를 읽기 위해 사용된다. Listing 2. invokeHelloScript 메소드
private static void invokeHelloScript(ScriptEngine jsEngine) throws ScriptException {
jsEngine.eval("println('Hello from xxJavaScript')");
}
|
|
스크립트 실행 콘텍스트
HelloScriptingWorld 애플리케이션의 예제 스크립트는 xxJavaScript println() 함수를 사용하여 콘솔로 출력하지만, 인풋과 아웃풋 스트림을 완전히 제어할 수 있다. 스크립트 엔진은 스크립트의 실행 콘텍스트를 수정할 수 있는 옵션을 제공하는데, 이것으로 표준 인풋, 표준 아웃풋, 표준 에러에 사용되는 스트림을 수정할 수 있고, 어떤 글로벌 변수와 자바 객체들이 실행 중인 스크립트에 사용될 수 있는지를 정의한다.
| |
invokeHelloScript() 메소드의 xxJavaScript는 Hello from xxJavaScript 를 표준 아웃풋 스트림으로 출력하는데, 이 경우는 콘솔 윈도우이다. (Listing 6에는 HelloScriptingWorldApplication 을 실행한 전체 결과가 포함되어 있다.)
이것과 클래스의 다른 메소드들은 javax.script.ScriptException 을 던진다는 것을 선언한다. 체크된 예외(스크립팅 패키지에 의해 정의된 단 한 개)는 엔진이 해당 코드를 파싱 또는 실행하지 못했다는 것을 나타낸다. 모든 script-engine eval() 메소드는 ScriptException 을 던진다는 것을 선언하기 때문에, 코드가 올바르게 핸들되도록 해야 한다.
Listing 3은 뒤 개의 관련 메소드들(defineScriptFunction() 과 invokeScriptFunctionFromEngine() 을 보여주고 있다. defineScriptFunction() 메소드는 script-engine의 eval() 메소드를 xxJavaScript의 하드 코딩된 스니펫으로 호출한다. 이 메소드는 xxJavaScript 함수인 sayHello() 를 정의할 뿐이다. 어떤 코드도 실행되지 않는다. sayHello() 함수는 하나의 매개변수를 취하는데, 이는 콘솔에 println() 문으로 출력한다. script-engine의 xxJavaScript 인터프리터는 이 함수를 글로벌 환경에 추가하면서, 후속 eval 호출에서 사용할 수 있도록 하는데, 이것은 invokeScriptFunctionFromEngine() 메소드에서 발생한다. Listing 3. defineScriptFunction과 invokeScriptFunctionFromEngine 메소드
private static void defineScriptFunction(ScriptEngine engine) throws ScriptException {
// Define a function in the script engine
engine.eval(
"function sayHello(name) {" +
" println('Hello, ' + name)" +
"}"
);
}
private static void invokeScriptFunctionFromEngine(ScriptEngine engine)
throws ScriptException
{
engine.eval("sayHello('World!')");
}
|
한 쌍의 메소드들이 스크립트 엔진이 애플리케이션 컴포넌트의 상태를 관리할 수 있고, 엔진의 eval() 메소드로의 연속 호출 동안 상태를 이용할 수 있다는 것을 나타내고 있다. invokeScriptFunctionFromEngine() 메소드는 eval() 로의 이전 호출에 정의된 sayHello() xxJavaScript 함수를 호출함으로써, 관리된 상태를 활용한다.
많은 스크립트 엔진들은 eval() 로의 호출들 사이에 글로벌 변수와 함수들의 상태를 관리한다. 하지만, 자바 스크립팅 API는 이 기능을 제공하는 script-engine을 필요로 하지 않는다. 이 글에 사용된 xxJavaScript, Groovy, JRuby 스크립트 엔진들은 eval() 로의 호출들 간 상태를 관리한다.
Listing 4는 선행 예제들의 변종이다. invokeScriptFunctionFromJava() 메소드는 ScriptEngine 의 eval() 메소드나 xxJavaScript 코드를 사용하지 않고 sayHello() xxJavaScript 함수를 호출한다는 점에서 다르다. 대신, 자바 스크립팅 API의 javax.script.Invocable 인터페이스를 사용하여 스크립트 엔진에 의해 관리되는 함수를 호출한다. invokeScriptFunctionFromJava() 메소드는 script-engine 객체를 Invocable 인터페이스로 던진 다음, 인터페이스에 invokeFunction() 메소드를 호출하여 주어진 매개변수를 사용하여 sayHello() xxJavaScript 함수를 호출한다. 호출된 함수가 값을 리턴하면, invokeFunction() 메소드는 자바 Object 유형으로서 래핑된 채로 리턴한다. Listing 4. invokeScriptFunctionFromJava 메소드
private static void invokeScriptFunctionFromJava(ScriptEngine engine)
throws ScriptException, NoSuchMethodException
{
Invocable invocableEngine = (Invocable) engine;
invocableEngine.invokeFunction("sayHello", "from Java");
}
|
|
프록시를 사용한 고급 스크립트 호출
스크립트 함수나 메소드가 자바 인터페이스를 구현할 때 Invocable 을 활용할 수 있다. Invocable 인터페이스는 매개변수로서 인터페이스를 취하는 getInterface() 메소드를 정의하고 제공된 인터페이스를 구현하는 자바 프록시 객체를 리턴한다. 스크립트 엔진에서 프록시 객체를 획득하면, 이것을 일반 자바 객체로 취급할 수 있다. 프록시에 호출된 메소드들은 스크립팅 언어에 의한 실행을 위해 스크립트 엔진으로 위임된다. | |
Listing 4에는 어떤 xxJavaScript도 없다. Invocable 인터페이스를 사용하면 자바 코드가 구현 언어를 알지 못해도 스크립트 함수를 호출할 수 있다. invokeFunction() 메소드는 스크립트 엔진이 주어진 이름 또는 매개변수 유형을 가진 함수를 찾지 못하면 java.lang.NoSuchMethodException 을 던진다.
자바 스크립팅 API는 Invocable 인터페이스를 구현하는 스크립트 엔진을 필요로 하지 않는다. 이상적으로는, Listing 4의 코드는 instanceof 연산자를 사용하여 스크립트 엔진이 캐스팅 하기 전에 Invocable 인터페이스를 구현했다.
스크립팅 코드에서 자바 메소드 호출하기
Listing 3과 Listing 4의 예제는 자바 코드가 스크립팅 언어에 정의된 함수나 메소드를 호출하는 방법을 보여주었다. 스크립팅 언어로 작성된 코드가 자바 객체에 메소드를 호출할 수 있는지도 궁금할 것이다. 할 수 있다. Listing 5의 invokeJavaFromScriptFunction() 메소드는 자바 객체로 스크립팅 엔진 액세스를 제공하는 방법과, 스크립팅 코드가 그 자바 객체에 메소드를 호출하는 방법을 나타내고 있다. 특히, invokeJavaFromScriptFunction() 메소드는 스크립트 엔진의 put() 메소드를 사용하여 HelloScriptingWorld 클래스의 인스턴스를 엔진에 제공한다. 엔진이 put() 으로의 호출 시 제공된 이름을 사용하여 자바 객체로 액세스 하면, eval() 메소드로의 호출 시 스크립팅 코드가 이것을 사용한다. Listing 5. invokeJavaFromScriptFunction과 getHelloReply 메소드
private static void invokeJavaFromScriptFunction(ScriptEngine engine)
throws ScriptException
{
engine.put("helloScriptingWorld", new HelloScriptingWorld());
engine.eval(
"println('Invoking getHelloReply method from xxJavaScript...');" +
"var msg = helloScriptingWorld.getHelloReply(vxxJavaScript');" +
"println('Java returned: ' + msg)"
);
}
/** Method invoked from the above script to return a string. */
public String getHelloReply(String name) {
return "Java method getHelloReply says, 'Hello, " + name + "'";
}
|
Listing 5의 eval() 메소드로의 호출에 포함된 xxJavaScript 코드는 스크립트 엔진의 put() 메소드로의 호출 시 제공된 변수 이름 HelloScriptingWorld 를 사용하여 여기에 액세스 함으로써 helloScriptingWorld 자바 객체를 사용한다. xxJavaScript 코드의 두 번째 라인은 getHelloReply() 퍼블릭 자바 메소드를 호출한다. (Listing 5) getHelloReply() 메소드는 Java method getHelloReply says, 'Hello, <parameter>' 스트링을 리턴한다. eval() 메소드의 xxJavaScript 코드는 자바 리턴 값을 msg 변수에 할당하고 그 값을 콘솔에 프린트 한다.
|
자바 객체 전환
스크립팅 엔진이 자바 객체가 엔진의 환경에서 실행되는 스크립트에 사용될 수 있을 때, 그 엔진은 이것을 스크립팅 언어에 적용할 수 있는 개체 유형으로 래핑해야 한다. 래핑에는 자바 Integer 객체가 스크립팅-언어 수학 식에서 직접 사용될 수 있도록 하는 것 같이, 적절한 객체-값 전환이 포함되어 있다. 자바 객체가 스크립팅 객체로 전환하는 방법은 각 스크립팅 언어 엔진 고유의 것이고 이 글의 범위도 아니다. 전환이 발생한다는 것을 인식하여, 여러분이 사용하는 스크립팅 언어가 여러분이 기대한 방식으로 변환을 수행하는지를 확인할 수 있다. | |
ScriptEngine.put 과 이것과 제휴된 get() 메소드는 스크립트 엔진 내에서 실행되는 자바 코드와 스크립트 간 객체와 데이터를 공유하는 기본적인 방식이다. (스크립트-실행 범위) 엔진의 put() 메소드를 호출하면, 스크립트 엔진은 두 번째 매개변수(자바 객체)와 주어진 스트링 키를 제휴시킨다. 대부분의 스크립트 엔진들은 그러한 자바 객체들이 주어진 변수 이름 하에서 스크립트에 액세스 할 수 있도록 한다. 스크립트 엔진들은 put() 메소드로 전달하는 이름들을 마음대로 고친다. 예를 들어, JRuby 스크립트 엔진은 글로벌 변수용 Ruby 신택스에 맞도록 글로벌 $helloScriptingWorld 변수 하에서 Ruby 코드에 helloScriptingWorld 가 사용될 수 있도록 한다.
스크립트 엔진의 get() 메소드는 스크립트 환경 내에서 사용할 수 있는 값들을 가져온다. 일반적으로, 스크립트 환경의 모든 변수와 함수들은 get() 메소드를 통해 자바 코드에서 액세스 할 수 있다. 하지만, put() 을 사용하여 스크립트 엔진과 명확하게 공유되는 자바 객체들만이 스크립트에 액세스 할 수 있다.
외부 스크립트가 실행 애플리케이션의 자바 객체에 액세스 하여 조작할 수 있는 기능은 자바 프로그램의 기능을 확장시킬 수 있는 강력한 기술이다. (Part 2의 예제는 이 기술을 활용하고 있다.)
HelloScriptingWorld 애플리케이션 실행하기
소스 코드를 다운로드 및 구현하여 HelloScriptingWorld 애플리케이션을 구동할 수 있다. .zip 파일에는 Ant 스크립트와 Maven 빌드 파일이 포함되어 있어 샘플 애플리케이션의 컴파일과 실행을 돕는다. 다음 단계를 따른다.
- .zip 파일을 다운로드 한다.
- java-scripting 같은 새로운 디렉토리를 만들어서, 1번 단계에서 다운로드 했던 파일을 이 디렉토리에서 압축을 푼다.
- 명령행 쉘을 열고 그 디렉토리로 변경한다.
ant run-hello 를 실행한다.
Ant에서 Listing 6과 비슷한 콘솔 아웃풋을 보게 될 것이다. defineScriptFunction() 메소드는 정의는 하지만 xxJavaScript 함수를 호출하지 않으므로 어떤 아웃풋도 만들지 않는다. Listing 6. HelloScriptingWorld 실행 결과
Calling invokeHelloScript...
Hello from xxJavaScript
Calling defineScriptFunction...
Calling invokeScriptFunctionFromEngine...
Hello, World!
Calling invokeScriptFunctionFromJava...
Hello, from Java
Calling invokeJavaFromScriptFunction...
Invoking getHelloReply method from xxJavaScript...
Java returned: Java method getHelloReply says, 'Hello, xxJavaScript'
|
Java 5 호환성
Java SE 6은 자바 스크립팅 API를 도입했지만, Java SE 5에서도 이 API를 실행할 수 있다. 누락된 javax.script 패키지 클래스의 구현을 제공하기만 하면 된다. 다행히도, Java Specification Request 223 레퍼런스 구현에서 가능하다. (참고자료) JSR 223은 자바 스크립팅 API를 정의한다.
JSR 223 레퍼런스 구현을 다운로드 했다면, 파일 압축을 풀고 script-api.jar, script-js.jar, js.jar 파일들을 classpath에 둔다. 이러한 파일들은 스크립트 API, xxJavaScript script-engine 인터페이스, xxJavaScript 스크립트 엔진(Java SE 6)을 제공한다.
스크립트 실행 범위
스크립트 엔진 내에서 실행 중인 스크립트에 자바 객체를 노출하는 방법은 엔진의 get() 과 put() 메소드를 호출하는 것보다 설정 가능하다. 스크립트 엔진에 get() 또는 put() 을 호출하면, 이 엔진은 javax.script.Bindings 인터페이스의 기본 인스턴스에 요청된 키를 검색 또는 저장한다. (Bindings 인터페이스는 키가 스트링이 되도록 하는 Map 인터페이스이다.)
코드가 스크립트 엔진의 eval() 메소드를 호출하면, 이 엔진의 기본 키 및 값 바인딩이 사용된다. 하지만, 고유의 Bindings 객체를 eval() 호출에 제공하여 지정된 스크립트에 보이는 객체와 변수들을 제한할 수 있다. 호출은 eval(String, Bindings) 또는 eval(Reader, Bindings) 이 된다. 커스터마이징 된 Bindings 를 생성하는 것을 돕기 위해, 스크립트 엔진은 빈 Bindings 객체를 리턴하는 createBindings() 메소드를 제공한다.Bindings 객체로 eval 을 호출하면 엔진의 기본 바인딩에 저장되었던 자바 객체를 임시로 숨긴다.
스크립트 엔진에는 두 개의 기본 바인딩이 포함된다. get() 과 put() 호출에 의해 사용되는 "엔진 범위" 바인딩과 "엔진 범위" 바인딩에서 발견되지 못할 경우 객체들을 검색하는데 사용할 수 있는 "글로벌 범위" 바인딩이다. 여기에서 할 수 있다라는 단어는 중요하다. 스크립트 엔진들은 스크립트에 글로벌 바인딩이 액세스 할 수 있도록 할 필요가 없다. 대부분의 스크립트 엔진들이 이를 수행한다.
"글로벌 범위" 바인딩의 디자인 목적은 다른 스크립트 엔진들 간 객체들을 공유하는 것이다. ScriptEngineManager 인스턴스에 의해 리턴된 모든 스크립트 엔진은 같은 "글로벌 범위" 바인딩 객체로 된다. getBindings(ScriptContext.GLOBAL_SCOPE) 메소드를 사용하여 엔진의 글로벌 바인딩을 검색할 수 있고 setBindings(Bindings, ScriptContext.GLOBAL_SCOPE) 을 사용하여 엔진용 글로벌 바인딩을 설정할 수 있다.
ScriptContext 는 스크립트 엔진의 런타임 콘텍스트를 정의 및 제어하는 인터페이스이다. 스크립트 엔진의 ScriptContext 에는 "엔진"과 "글로벌" 범위 바인딩은 물론, 엔진이 표준 인풋과 아웃풋 연산에 사용하는 인풋 및 아웃풋 스트림이 포함된다. 엔진의 getContext() 메소드를 사용하여 스크립트 엔진의 콘텍스트를 얻고 조작할 수 있다.
범위, 바인딩, 콘텍스트 같은 스크립팅 API 개념들은 중복되는 의미 때문에 혼란스러울 수 있다. 소스-코드 다운로드 파일에는 src/test/java 디렉토리에 있는 ScriptApiRhinoTest 라고 하는 JUnit 테스트 파일이 포함되어 있고 자바 코드를 통해 이러한 개념들을 설명하고 있다.
다음에는?
자바 스크립팅 API에 대한 기초를 배웠으므로, Part 2에서는 이러한 지식을 확장하여 보다 현실적인 샘플 애플리케이션을 사용해 보도록 하겠다. 이 애플리케이션은 Groovy, Ruby, xxJavaScript를 결합하여 작성된 외부 스크립트 파일을 사용하여 런타임 시 변경될 수 있는 비즈니스 로직을 정의한다. 여러분도 보듯, 스크립팅 언어로 비즈니스 규칙을 정의하면 규칙을 더욱 쉽게 작성할 수 있고, 비즈니스 분석가나 규칙 작성자 같은 비 프로그래머들도 쉽게 읽을 수 있다.
다운로드 하십시오
설명 |
이름 |
크기 |
다운로드 방식 |
소스 코드와 JAR 파일 |
j-xxjavascripting1.zip |
116KB |
HTTP | |