NashornからGraalJSへの移行ガイド

このガイドは、以前にNashornエンジンを対象としていたコードの移行ガイドとして機能します。サポートされているJava相互運用機能の概要については、Java相互運用性ガイドを参照してください。

Nashornエンジンは、JEP 335の一部としてJDK 11で非推奨となり、JEP 372の一部としてJDK15から削除されました。

GraalJSは、以前にNashornエンジンで実行されていたJavaScriptコードの代替として使用できます。GraalJSは、以前Nashornによって提供されていたJavaScriptのすべての機能を提供します。多くはデフォルトで利用可能ですが、一部はオプションの後ろにあり、他のものはソースコードにわずかな変更を加える必要があります。

NashornとGraalJSはどちらも、Java相互運用性に対して同様の構文とセマンティクスをサポートしています。注目すべき違いの1つは、GraalJSがデフォルトで安全なアプローチを取っていることです。つまり、Nashornでデフォルトで利用可能だった一部の機能は、明示的に有効にする必要があります。移行に関連する最も重要な違いを以下に示します。

デフォルトで利用可能なNashorn機能(セキュリティ設定に依存)

  • Java.type, Java.typeName
  • Java.from, Java.to
  • Java.extend, Java.super
  • Javaパッケージグローバル変数:Packagesjavajavafxjavaxcomorgedu

Nashorn互換モード #

GraalJSはNashorn互換モードを提供します。Nashorn互換に必要な機能の一部は、js.nashorn-compatオプションが有効になっている場合にのみ利用できます。これは、GraalJSがデフォルトで公開したくないNashorn固有の拡張機能の場合です。

このオプションを使用するには、実験的な機能をアンロックする必要があることに注意してください。さらに、このオプションを設定すると、たとえばレガシーScriptEngineで動作する場合など、GraalJSのデフォルトで安全なアプローチが一部の場合に損なわれることに注意してください。

Nashorn互換モードを使用する場合、デフォルトでは、ECMAScript 5が互換性レベルとして設定されます。js.ecmascript-versionオプションを使用して、別のECMAScriptバージョンを指定できます。これにより、Nashornとの完全な互換性が損なわれる可能性があることに注意してください。オプションを設定する方法のコード例は、このセクションの最後に示されています。

js.nashorn-compatオプションは、次のように設定できます。

  • コマンドラインオプションを使用する
      js --experimental-options --js.nashorn-compat=true
    
  • Polyglot APIを使用する
      import org.graalvm.polyglot.Context;
    
      try (Context context = Context.newBuilder().allowExperimentalOptions(true).option("js.nashorn-compat", "true").build()) {
          context.eval("js", "print(__LINE__)");
      }
    
  • Javaアプリケーションの起動時にシステムプロパティを使用する(アプリケーションのContext.BuilderallowExperimentalOptionsを有効にすることも忘れないでください)
      java -Dpolyglot.js.nashorn-compat=true MyApplication
    

nashorn-compatオプションでのみ利用可能な機能には、以下が含まれます。

  • Java.isJavaFunctionJava.isJavaMethodJava.isScriptObjectJava.isScriptFunction
  • new Interface|AbstractClass(fn|obj)
  • JavaImporter
  • JSAdapter
  • 文字列値に対するjava.lang.Stringメソッド
  • load("nashorn:parser.js")load("nashorn:mozilla_compat.js")
  • exitquit

js.ecmascript-versionオプションは、同様の方法で設定できます。これはサポートされているオプションであるため、ecmascript-versionを設定するためだけにexperimental-optionsオプションを提供する必要はありません。

js --js.ecmascript-version=2020

Nashorn構文拡張機能 #

Nashorn構文拡張機能は、js.syntax-extensions実験的オプションを使用して有効にできます。これらは、Nashorn互換モード(js.nashorn-compat)でもデフォルトで有効になっています。

GraalJSとNashornの比較 #

GraalJSは、意図的な設計上の決定であるいくつかの点でNashornとは異なります。

デフォルトで安全 #

GraalJSはデフォルトで安全なアプローチを採用しています。埋め込みによって明示的に許可されない限り、JavaScriptコードはJavaクラスにアクセスしたり、ファイルシステムにアクセスしたりすることはできません。Nashorn互換機能を含むGraalJSのいくつかの機能は、関連するセキュリティ設定が十分に許容範囲である場合にのみ使用できます。

セキュアなデフォルト制限をアプリケーションおよびホストシステムに解除する変更によるセキュリティへの影響を必ず理解してください。

利用可能な設定の完全なリストについては、Context.Builderを参照してください。これらのオプションは、Polyglot APIでコンテキストを構築するときに定義できます。

GraalJSの機能を有効にするためによく必要なオプションは次のとおりです。

  • allowHostAccess():ゲストアプリケーションからアクセスできるパブリッククラスのパブリックコンストラクター、メソッド、またはフィールドを設定します。アクセスを選択的に有効にするには、HostAccess.EXPLICITまたはカスタムHostAccessポリシーを使用します。制限のないアクセスを許可するには、HostAccess.ALLに設定します。
  • allowHostClassLookup():ゲストアプリケーションによってルックアップできるJavaホストクラスを指定するフィルターを設定します。すべてのクラスのルックアップを許可するには、述語className -> trueに設定します。
  • allowIO():ゲスト言語がホストシステムで無制限のIO操作を実行できるようにします。たとえば、ファイルシステムからload()するために必要です。IOを有効にするには、trueに設定します。

レガシーScriptEngineでコードを実行する場合は、そこでの設定方法に関するBindingsを介したオプションの設定を参照してください。

最後に、nashorn-compatモードでは、ScriptEngineContextではなく)でコードを実行するときに関連するオプションが有効になり、そのセットアップでNashornとの互換性が向上することに注意してください。

ランチャー名 js #

GraalJSには、jsという名前のバイナリランチャーが付属しています。ビルド環境によっては、GraalJSは引き続きNashornとそのjjsランチャーを出荷する場合があることに注意してください。

ScriptEngine名 graal.js #

GraalJSはScriptEngineのサポートとともに提供されます。これは、「graal.js」、「JavaScript」、「js」を含むいくつかの名前で登録されます。完全なNashorn互換性が必要な場合は、上記のようにNashorn互換モードをアクティブにしてください。ビルドセットアップによっては、GraalJSは引き続きNashornを出荷し、ScriptEngine経由で提供する場合があります。詳細については、ScriptEngineの実装を参照してください。

ClassFilter #

GraalJSは、ポリグロットContextで起動するときにクラスフィルターを提供します。Context.Builder.hostClassFilterを参照してください。

完全修飾名 #

GraalJSでは、Java.type(typename)を使用する必要があります。デフォルトでは、完全修飾クラス名だけでクラスにアクセスすることはサポートされていません。Java.typeはより明確になり、JavaScriptコードでのJavaクラスの誤った使用を回避できます。たとえば、このパターンを見てください

var bd = new java.math.BigDecimal('10');

次のように表現する必要があります

var BigDecimal = Java.type('java.math.BigDecimal');
var bd = new BigDecimal('10');

損失のある変換 #

GraalJSでは、Javaメソッドを呼び出すときに、引数の損失のある変換は許可されていません。これにより、検出が困難な数値のバグが発生する可能性があります。

GraalJSは常に、損失なしで変換できる引数型が最も狭いオーバーロードされたメソッドを選択します。そのようなオーバーロードされたメソッドが利用できない場合、GraalJSは損失のある変換の代わりにTypeErrorをスローします。一般に、これはどのオーバーロードされたメソッドが実行されるかに影響します。

カスタムtargetTypeMappingを使用して、動作をカスタマイズできます。HostAccess.Builder#targetTypeMappingを参照してください。

ScriptObjectMirrorオブジェクト #

GraalJSは、ScriptObjectMirrorクラスのオブジェクトを提供しません。代わりに、JavaScriptオブジェクトは、JavaのMapインターフェイスを実装するオブジェクトとしてJavaコードに公開されます。

ScriptObjectMirrorインスタンスを参照するコードは、タイプをインターフェイス(MapまたはList)または同様の機能を提供するポリグロットValueクラスのいずれかに変更することで書き換えることができます。

マルチスレッディング #

GraalVMでのJavaScriptの実行は、Javaコードから複数のContextオブジェクトを作成することにより、マルチスレッドをサポートします。コンテキストはスレッド間で共有できますが、各コンテキストは一度に1つのスレッドによってアクセスされる必要があります。複数のJavaScriptエンジンをJavaアプリケーションから作成でき、複数のスレッドで並行して安全に実行できます。

Context polyglot = Context.create();
Value array = polyglot.eval("js", "[1,2,42,4]");

GraalJSでは、現在のContextへのアクセスを許可するJavaScriptからのスレッドの作成は許可されていません。さらに、GraalJSでは、同時実行スレッドが同じContextに同時にアクセスすることは許可されていません。これにより、マルチスレッドに対応していない言語で、データ競合などの管理不可能な同期の問題が発生する可能性があります。例えば

new Thread(function() {
    print('printed from another thread'); // throws Exception due to potential synchronization problems
}).start();

JavaScriptコードは、Javaで実装されたRunnableを使用してスレッドを作成および開始できます。子スレッドは、親スレッドまたは他のポリグロットスレッドのContextにアクセスできません。違反した場合、IllegalStateExceptionがスローされます。ただし、子スレッドは新しいContextインスタンスを作成できます。

new Thread(aJavaRunnable).start(); // allowed on GraalJS

適切な同期が行われている場合、複数のコンテキストを異なるスレッド間で共有できます。複数のスレッドからJavaScript Contextを使用するJavaアプリケーションの例は、こちらにあります。

Nashorn互換モードでのみ利用可能な拡張機能 #

Nashorn で利用可能だった以下の JavaScript 拡張機能は、デフォルトでは GraalJS では無効になっています。これらは Nashorn 互換モードで提供されます。これらの機能に基づいて新しいアプリケーションを実装することは強く推奨されず、既存のアプリケーションを GraalVM に移行する手段としてのみ使用してください。

String の length プロパティ #

GraalJS は、String の length プロパティを特別に扱いません。String の長さにアクセスする正規の方法は、length プロパティを読み取ることです。

myJavaString.length;

Nashorn では、ユーザーは length にプロパティとしても関数としてもアクセスできます。既存の関数呼び出し length() は、プロパティアクセスとして表現する必要があります。Nashorn の動作は、Nashorn 互換モードで模倣されています。

JavaScript グローバルオブジェクトの Java パッケージ #

GraalJS では、完全修飾名の代わりに Java.type を使用する必要があります。Nashorn 互換モードでは、以下の Java パッケージが JavaScript グローバルオブジェクトに追加されます: java, javafx, javax, com, org, および edu

JavaImporter #

JavaImporter 機能は、Nashorn 互換モードでのみ利用可能です。

JSAdapter #

非標準の JSAdapter 機能の使用は推奨されず、同等の標準の Proxy 機能に置き換える必要があります。互換性のために、JSAdapter は Nashorn 互換モードで引き続き利用可能です。

Java.* メソッド #

Java グローバルオブジェクトで Nashorn によって提供されるいくつかのメソッドは、Nashorn 互換モードでのみ利用可能であるか、現在 GraalJS でサポートされていません。Nashorn 互換モードで利用可能なのは、Java.isJavaFunction, Java.isJavaMethod, Java.isScriptObject, および Java.isScriptFunction です。Java.asJSONCompatible は現在サポートされていません。

アクセサー #

Nashorn 互換モードでは、GraalJS では、ユーザーは getset、または is を省略して、プロパティとして名前を使用するだけでゲッターとセッターにアクセスできます。

var Date = Java.type('java.util.Date');
var date = new Date();

var myYear = date.year; // calls date.getYear()
date.year = myYear + 1; // calls date.setYear(myYear + 1);

GraalJS は、アクセスの順序に関して Nashorn の動作を模倣します。

  • 読み取り操作の場合、GraalJS は最初に、get という名前とキャメルケースのプロパティ名を持つゲッターを呼び出そうとします。それが利用できない場合、is という名前とキャメルケースのプロパティ名を持つゲッターが呼び出されます。2 番目のケースでは、Nashorn とは異なり、結果の値が boolean 型でなくても返されます。両方のメソッドが利用できない場合にのみ、プロパティ自体が読み取られます。
  • 書き込み操作の場合、GraalJS は、set という名前とキャメルケースのプロパティ名を持つセッターを呼び出し、その関数に引数として値を提供しようとします。セッターが利用できない場合、プロパティ自体が書き込まれます。

Nashorn(したがって、GraalJS)は、プロパティの読み取り/書き込みと関数呼び出しを明確に区別していることに注意してください。Java クラスに、同じ名前のフィールドとメソッドの両方が公開されている場合、obj.property は常にフィールド(または上記で説明したゲッター)を読み取り、obj.property() は常にそれぞれのメソッドを呼び出します。

考慮すべき追加の側面 #

GraalJS の機能 #

GraalJS は、最新の ECMAScript 仕様の機能と、いくつかの拡張機能をサポートしています。詳しくは、JavaScript Compatibility を参照してください。この例では、これらの拡張機能を認識していない既存のソースコードに干渉する可能性のあるオブジェクトをグローバルスコープに追加することに注意してください。

コンソール出力 #

GraalJS は、Nashorn と互換性のある print 組み込み関数を提供します。

GraalJS は console.log 関数も提供することに注意してください。これは純粋な JavaScript モードでは print のエイリアスですが、Node モードで実行する場合は Node.js によって提供される実装を使用します。Node.js は Java オブジェクトの特別な処理を実装していないため、Node モードでは console.log の Java オブジェクトに関する動作が異なります。

お問い合わせ