ScriptEngine実装

GraalVMでは、JavaScriptを実行するためのJSR-223準拠のjavax.script.ScriptEngine実装が提供されています。この機能は、現在ScriptEngineに基づく実装を簡単に移行できるようにするために、レガシーの理由から提供されていることに注意してください。設定の多くを直接制御し、GraalVMのよりきめ細かいセキュリティ設定のメリットを得ることができるように、org.graalvm.polyglot.Contextインタフェースを使用することをお薦めします。

前提条件

ノート: GraalVM for JDK 21では、GraalVMにデフォルトでScriptEngineが含まれなくなりました。それに依存する場合は、設定をスクリプト・エンジン・モジュールに明示的に依存するように移行し、モジュール・パスに追加する必要があります。

js-scriptengineモジュールを取得するには、次のようにMaven依存関係を使用します。

<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js-scriptengine</artifactId>
    <version>${graalvm.version}</version>
</dependency>
<dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>${graalvm.version}</version>
    <scope>runtime</scope>
</dependency>

mvnを使用しない場合は、js-scriptengine.jarファイルをモジュール・パスに手動で追加する必要があります(--module-path=languages/js/graaljs-scriptengine.jarなど)。場合によっては、ScriptEngineが見つかるように、コマンド・ラインに--add-modules org.graalvm.js.scriptengineを追加する必要もあります。org.graalvm.js.scriptengineモジュールへの明示的な依存関係は、GraalJSScriptEngineを直接使用する場合にのみ必要です(次を参照)。最後に、jlinkを使用して、JS ScriptEngineを含むカスタムJavaランタイム・イメージを生成することもできます。

pom.xmlの例は、GitHubのGraalJSリポジトリにあります。

推奨: CompiledScript APIの使用

JSソースの不要な再コンパイルを回避するには、ScriptEngine.evalのかわりにCompiledScript.evalを使用することをお薦めします。これにより、対応するCompiledScriptオブジェクトが存続しているかぎり、JITコンパイル済コードがガベージ・コレクションされなくなります。

単一スレッドの例:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
CompiledScript script = ((Compilable) engine).compile("console.log('hello world');");
script.eval();

マルチスレッドの例:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
CompiledScript script = ((Compilable) engine).compile("console.log('start');var start = Date.now(); while (Date.now()-start < 2000);console.log('end');");
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            // Create ScriptEngine for this thread (with a shared polyglot Engine)
            ScriptEngine engine = manager.getEngineByName("js");
            script.eval(engine.getContext());
        } catch (ScriptException scriptException) {
            scriptException.printStackTrace();
        }
    }
}).start();
script.eval();

Bindingsを介したオプションの設定

ScriptEngineインタフェースには、オプションを設定するデフォルトの方法がありません。回避策として、GraalJSScriptEngineでは、Bindingsによる一部のContextオプションの設定がサポートされています。オプションは次のとおりです:

これらのオプションは、評価されたJavaScriptコードに適用されるサンドボックス化ルールを制御し、アプリケーションがNashorn互換性モード(--js.nashorn-compat=true)で起動されていないかぎり、デフォルトではfalseに設定されます。

ScriptEngineを使用すると、暗黙的に試験段階のオプションが許可されることに注意してください。これは、Bindingsを介して渡すことができるオプションを網羅したリストであり、GraalVM JavaScriptに追加オプションを渡す必要がある場合は、次に示すようにContextを手動で作成する必要があります。

Bindingsを介してオプションを設定するには、エンジンのスクリプト・コンテキストが初期化されるに、Bindings.put(<option name>, true)を使用します。Bindings#get(String)のコールでもコンテキストの初期化につながる場合があることに注意してください。次のコードは、Bindingsを介してpolyglot.js.allowHostAccessを有効にする方法を示しています:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
bindings.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // it will not work without allowHostAccess and allowHostClassLookup

この例は、ユーザーがbindings.put("polyglot.js.allowHostAccess", true);をコールする前に、たとえばengine.eval("var x = 1;")をコールした場合は機能しませんが、これはevalをコールするとコンテキストが強制的に初期化されるためです。

システム・プロパティを介したオプションの設定

JavaScriptエンジンに対するオプションは、先頭にpolyglot.を付加することで、JVMを起動する前にシステム・プロパティを介して設定できます:

java -Dpolyglot.js.ecmascript-version=2022 MyApplication

または、JavaScriptエンジンに対するオプションは、ScriptEngineを作成する前にJava内からプログラムで設定できます。ただし、これはJavaScriptエンジンに渡されるオプション(js.ecmascriptなど)にのみ機能し、Bindingsを介して設定できる前述の6つのオプションには機能しません。また、これらのシステム・プロパティは、同時に実行されるすべてのScriptEngineによって共有されることに注意してください。

柔軟性を高めるためのContextの手動作成

Contextのオプションは、Context.Builderのインスタンスを介してGraalJSScriptEngineに直接渡すこともできます:

ScriptEngine engine = GraalJSScriptEngine.create(null,
        Context.newBuilder("js")
        .allowHostAccess(HostAccess.ALL)
        .allowHostClassLookup(s -> true)
        .option("js.ecmascript-version", "2022"));
engine.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));");

これにより、GraalVM JavaScriptで使用可能なすべてのオプションを設定できます。これには、GraalJSScriptEngineクラスやContextクラスなど、GraalVM JavaScriptに対する強い依存性が伴います。

サポートされるファイル拡張子

javax.script.ScriptEngineのGraalVM JavaScript実装では、JavaScriptソース・ファイルのjsファイル拡張子、およびESモジュールのmjs拡張子がサポートされています。