nashorn のグローバルスコープ
こんにちは、開発担当の Masa です。
最近 Java8 の ScriptEngine(nashorn) を調査していますが、グローバルスコープ
関連でつまずいたので現象と回避方法を紹介します。
以下はグローバルスコープのオブジェクトを参照したり、Java オブジェクトを生成する
サンプルです。
※本記事のサンプルは Java SE 8u51 で実行しました。
まずはスクリプトからアクセスする Java クラスを適当に作成します。
package jp.co.hos.sample2;
public class SampleMain2 { }
次にスクリプトを実行する側を実装します。
ScriptManager の Bindings を使ってグローバルスコープにオブジェクトを登録して
います。
package jp.co.hos.sample1;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class Sample1 {
public static void main(String[] args) {
ScriptEngineManager manager = new ScriptEngineManager();
try {
// グローバルスコープにオブジェクトを登録します
manager.getBindings().put("global", "global scope");
ScriptEngine engine1 = manager.getEngineByName("nashorn");
// グローバルスコープのオブジェクトが参照できるか確認します
engine1.eval("print('engine1 output:' + global);");
ScriptEngine engine2 = manager.getEngineByName("nashorn");
// Java のクラスを参照できるか確認します
engine2.eval("load(\"nashorn:mozilla_compat.js\");");
engine2.eval("importPackage(Packages.jp.co.hos.sample2);");
engine2.eval("var sample2 = new SampleMain2();");
// グローバルスコープのオブジェクトが参照できるか確認します
engine2.eval("print('engine2 output:' + global);");
} catch (Exception e) {
e.printStackTrace();
}
}
}
実行結果:
engine1 output:global scope
engine2 output:global scope
正常に動作します。
ScriptManager の Bindings に直接オブジェクトを登録している部分を、engine1 で
定義したオブジェクトを Bindings ごしに登録するように修正します。
ScriptManager の Bindings に engine1 の Bindings(エンジンスコープ)を設定
しています。
ScriptEngineManager manager = new ScriptEngineManager();
try {
ScriptEngine engine1 = manager.getEngineByName("nashorn");
// グローバルスコープにオブジェクトを登録します
engine1.eval("var global = 'global scope';");
manager.setBindings(engine1.getBindings(ScriptContext.ENGINE_SCOPE));
// グローバルスコープのオブジェクトが参照できるか確認します
実行結果:
engine1 output:global scope
engine2 output:global scope
これも正常に動作します。
そこで、次の一行を追加します。
ScriptEngine engine1 = manager.getEngineByName("nashorn");
engine1.eval("load(\"nashorn:mozilla_compat.js\");");
実行結果:
javax.script.ScriptException: ReferenceError: “SampleMain2” is not defined in
engine1 で互換性モジュールを読み込んだだけでエラーになりました。
エラーの発生箇所は engine2 で SampleMain2 のインスタンス生成スクリプトを評価
しているところです。
load で評価した mozilla_compat.js の内容が衝突してる??いやいや、そんなはずは…
とりあえず、現象とエラー内容からグローバルスコープが怪しいのはわかるので対策を。
load の変わりに loadWithNewGlobal を使用することで新しいグローバル・オブジェクトを
使用して mozilla_compat.js が評価されます。
※これはあくまで例です。この方法では engine1 で importPackage を評価することが
できません。
ScriptEngine engine1 = manager.getEngineByName("nashorn");
engine1.eval("loadWithNewGlobal(\"nashorn:mozilla_compat.js\");");
// グローバルスコープにオブジェクトを登録します
engine1.eval("var global = 'global scope';");
manager.setBindings(engine1.getBindings(ScriptContext.ENGINE_SCOPE));
または、以下のように ScriptManager の Bindings に engine1 の Bindings 内のマッピング
を全て追加します。
ScriptEngine engine1 = manager.getEngineByName("nashorn");
engine1.eval("load(\"nashorn:mozilla_compat.js\");");
// グローバルスコープにオブジェクトを登録します
engine1.eval("var global = 'global scope';");
manager.getBindings().putAll(engine1.getBindings(ScriptContext.ENGINE_SCOPE));
実行結果:
engine1 output:global scope
engine2 output:global scope
どちらも正常に動作しました。
尚、mozilla_compat.js と importPackage は推奨されていません。