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 は推奨されていません。