목공책 하나 들이셔요~

2014년 9월 29일 월요일

Java Scripting API 둘러보기 #1

요즘 Java Scripting API를 이용한 프로젝트를 진행 중이어서 관련하여 도움받았던 텍스트를 번역해서 몇회에 나누어 올립니다. 원 글은 Jeff Friesen이 2007년에 작성한 것이며 당시 새로 발표된 Java 6에 이 Scripting API가 채택되어 이 내용을 소개한 글입니다. 제목은 "Taming Mustang, Part 2: Scripting API Tour"입니다.

Java 6의 코드명이 Mustang 즉 야생마였습니다. Taming Mustang은 야생마 길들이기라는 뜻입니다. 지금은 Java 8이 나왔기 때문에 다소 옛날 내용이긴 합니다만 API 자체는 거의 변화가 없기 때문에 여전히 유효한 내용입니다. 예제 코드들은 Java 8 환경에서 다시 검증하고, Java 8에 맞는 내용도 몇몇 보강하도록 하겠습니다. 원문의 URL은 다음과 같습니다.

http://www.informit.com/articles/article.aspx?p=696621

앞선 글에서 Java 6의 새로운 기능들에 대해서 알아보았는데 이제부터는 Java 6가 새로 소개하는 Scripting API에 대해 소개드릴까 합니다. Scripting API를 이용하면 당신의 코드를 일부는 Java로, 일부는 스크립트 언어로 개발할 수 있습니다. 이런 하이브리드 개발 방법은 Java의 막강한 API를 활용하면서도 스크립트의 유연한 동적 기능을 활용할 수 있는 장점이 있습니다.

Scripting API 둘러보기

Java Scripting API는 JCP(Java Community Process)에 의해 JSR(Java Specification Request)-223으로 정리되고 Java 6에 채용되었습니다. 이 요구사항(JSR)의 목적은 "스크립트 언어가 Java 플랫폼 내부의 정보에 접근하는 방법을 명시하고, Java의 서버측 어플리케이션에서 스크립트 언어가 쓰일 수 있는 방법을 제공하는 것"입니다.

JSR-223의 초기 버전인 WSF(Web Scripting Framework, javax.scripting.http 패키지)가 소개되었고 서블릿 컨테이너에서 웹 컨텐츠를 만드는데 사용되었지만, 스펙이 확정되어 가면서 옵션 사양으로 바뀝니다. 그래서 Java 6에 WSF는 포함되지 않았으며, 이에 대한 언급도 하지 않겠습니다.

Scripting API는 javax.script 패키지에 위치합니다. 이 패키지는 6개의 클래스와 6개의 인터페이스로 구성됩니다. 이 글이 진행되는 동안 이들에 대해 모두 훑어볼 예정입니다.

인터페이스로는 Bindings, Compilable, Invocable, ScriptContext, ScriptEngine, ScriptEngineFactory 등이 있고, 클래스로는 AbstractScriptEngine, CompiledScript, ScriptEngineManager, SimpleBindings, SimpleScriptContext 그리고 ScriptException 등이 있습니다.

Java 6에는 javax.script에 정의된 API들 외에도 실제 스크립트 엔진 구현체도 포함되어 있습니다. 이 스크립트 엔진은 JavaScript 엔진으로 Mozilla의 Rhino 스크립트 엔진입니다. (Rhino는 Java 7까지 쓰였고, Java 8 부터는 성능이 대폭 개선된 Nashorn 엔진으로 변경되었습니다. 하지만 구현체만 바뀌었을 뿐 JavaScript 언어 자체는 큰 변화가 없습니다)

ScriptEngine 얻기

javax.script 패키지에 있는 클래스들을 얻기 위한 입구는 ScriptEngineManager 클래스입니다. 이 클래스는 두개의 생성자가 있으며 지정한 스크립트 엔진의 팩토리를 찾는 역할을 합니다. 두개의 생성자는 다음과 같습니다.
  • public ScriptEngineManager() : 현재 쓰레드의 클래스로더나 부트스트랩 클래스로더로부터 스크립트 엔진 팩토리를 찾습니다. 
  • public ScriptEngineManager(ClassLoader loader) : 인자로 주어진 클래스로더로부터 팩토리를 찾습니다. 만일 null로 인자를 넣으면 위의 디폴트 생성자와 같은 행동을 합니다. 
ScriptEngineManager 인스턴스를 만들고 나서 getEngineFactories() 메쏘드를 호출하면 찾을 수 있는 모든 스크립트 엔진의 팩토리 즉 ScriptEngineFactory들의 List를 얻을 수 있습니다. 아래 예제는 스크립트 엔진 팩토리를 얻는 방법을 보여 줍니다.

// ScriptDemo1.java

import java.util.*;
import javax.script.*;

public class ScriptDemo1 {
    public static void main(String[] args) {
        // Create a ScriptEngineManager that discovers all script engine
        // factories (and their associated script engines) that are visible to
        // the current thread's classloader.

        ScriptEngineManager manager = new ScriptEngineManager();

        // Obtain a list of all discovered factories as ScriptEngineFactories.
        List<ScriptEngineFactory> factories = manager.getEngineFactories();

        // Display each script engine factory's metadata and create the script engine.
        for (ScriptEngineFactory factory : factories) {
            System.out.println("Full name = " + factory.getEngineName());
            System.out.println("Version = " + factory.getEngineVersion());
            System.out.println("Extensions");
            List<String> extensions = factory.getExtensions();
            for (String extension : extensions) {
                System.out.println("   " + extension);
            }
            System.out.println("Language name = " + factory.getLanguageName());
            System.out.println("Language version = " + factory.getLanguageVersion());
            System.out.println("MIME Types");
            List<String> mimetypes = factory.getMimeTypes();
            for (String mimetype : mimetypes) {
                System.out.println("   " + mimetype);
            }
            System.out.println("Short Names");
            List<String> shortnames = factory.getNames();
            for (String shortname : shortnames) {
                System.out.println("   " + shortname);
            }
            String[] params = {
                        ScriptEngine.ENGINE,
                        ScriptEngine.ENGINE_VERSION,
                        ScriptEngine.LANGUAGE,
                        ScriptEngine.LANGUAGE_VERSION,
                        ScriptEngine.NAME,
                        "THREADING"
                    };
            for (String param : params) {
                System.out.printf("Parameter %s = %s", param, factory.getParameter(param));
                System.out.println();
            }

            ScriptEngine engine = factory.getScriptEngine();
            System.out.println(engine);
            System.out.println();
        }
    }
}

ScriptDemo1 예제는 ScriptEngineFactory의 getEngineName()과 getEngineVersion() 함수를 불러 스크립트 엔진 구현체가 무엇인지를 알아냅니다. 반면에 getLanguageName()과 getLanguageVersion() 함수는 스크립트 언어에 대한 정체를 알아냅니다.

getExtensions(), getMimeTypes(), getNames() 함수는 스크립트 엔진을 찾는 키워드를 알아낼 수 있습니다. 스크립트 엔진은 스크립트 언어 파일의 확장자나 MIME 타입 혹은 스크립트 언어가 불리는 이름에 의해서 지정할 수 있습니다.

마지막으로 getParameter() 함수를 호출하여 스크립트 엔진 팩토리의 속성을 읽어내는데 예를 들어 ScriptEngine.ENGINE을 키로 입력을 넣으면 getEngineName() 부른 것과 같은 결과를 리턴하게 됩니다.

대응되는 함수가 없는 파라미터는 THREADING인데 이 파라미터값이 null이면 스크립트 실행에 있어 Thread-safe하지 않음을 의미하고, 다른 경우는 "MULTITHREADING", "THREAD-ISOLATED", "STATELESS" 등의 값이 지정될 수 있습니다.

마지막으로 getScriptEngine()을 호출하여 실제 ScriptEngine을 얻어냅니다. 이후로 이 ScriptEngine을 이용하여 스크립트를 실행할 수 있습니다. 다음은 Java 8에서 위 ScriptDemo1 을 실행한 결과입니다.

Full name = Oracle Nashorn
Version = 1.8.0_05
Extensions
   js
Language name = ECMAScript
Language version = ECMA - 262 Edition 5.1
MIME Types
   application/javascript
   application/ecmascript
   text/javascript
   text/ecmascript
Short Names
   nashorn
   Nashorn
   js
   JS
   JavaScript
   javascript
   ECMAScript
   ecmascript
Parameter javax.script.engine = Oracle Nashorn
Parameter javax.script.engine_version = 1.8.0_05
Parameter javax.script.language = ECMAScript
Parameter javax.script.language_version = ECMA - 262 Edition 5.1
Parameter javax.script.name = javascript
Parameter THREADING = null
jdk.nashorn.api.scripting.NashornScriptEngine@149494d8

실제로 ScriptEngine을 얻을 때는 위와 같은 열거용 함수를 쓰지 않고 직접 이름이나, 확장자나, MIME 타입을 명시하여 엔진을 얻어냅니다.

  • getEngineByExtension(String extension) : 스크립트 파일의 확장자로 엔진을 얻어냅니다. 위의 Nashorn 엔진의 경우 "js"를 넣으면 얻을 수 있습니다. 
  • getEngineByMimeType(String mimeType): 스크립트 파일의 MIME 타입으로 엔진을 얻습니다. Nashorn의 경우 "application/javascript", "applicaion/ecmascript", "text/javascript", "text/ecmascript"를 넣으면 됩니다. 
  • getEngineByName(String shortName) : 스크립트 엔진을 나타내는 짧은 이름을 넣어 엔진을 찾습니다. Nashorn의 경우 "nashorn", "Nashorn", "js", "JS", "JavaScript", "javascript", "ECMAScript", "ecmascript"등이 가능합니다. 
getEngineByExtension()의 경우 파일 열기 대화상자를 통해 스크립트 파일을 선택할 경우 유용합니다. 마찬가지고 getEngineByMimeType()의 경우 HTTP등의 네트웍을 통해 전달되는 스크립트의 형식을 얻어 엔진을 선택할 경우 유용합니다. 일반적인 경우는 getEngineByName()을 사용하는 것이 편합니다.

댓글 없음:

댓글 쓰기