NashornからGraalVM JavaScriptへの移行ガイド
このガイドは、これまでNashornエンジンにターゲット指定していたコードの移行ガイドとして役立ちます。サポートされているJavaの相互運用性機能の概要は、「Javaの相互運用性」ガイドを参照してください。
Nashornエンジンは、JEP 335の一環としてJDK11で非推奨に指定され、JEP 372の一環としてJDK15から削除されました。
GraalVMでは、これまでNashornエンジンで実行していたJavaScriptコードをかわりに実行できます。GraalVMには、以前にNashornによって提供されたJavaScriptのすべての機能が備えられています。多くはデフォルトで使用可能で、一部はフラグによる制御を伴い、その他はソース・コードに対する軽微な変更を必要とします。
Javaの相互運用性確保のために、NashornとGraalVM JavaScriptの両方で類似の構文およびセマンティクスのセットがサポートされています。大きな違いは、GraalVM JavaScriptではデフォルトでのセキュリティ保護(secure by default)アプローチが使用され、Nashornでデフォルトで使用可能であった一部の機能を明示的に有効にする必要があることです。移行に関連する最も重要な相違点を次に示します。
デフォルトで使用可能なNashorn機能は次のとおりです(セキュリティ設定によって異なる):
Java.type、Java.typeNameJava.from、Java.toJava.extend、Java.super- Javaパッケージ・グローバル: 
Packages、java、javafx、javax、com、org、edu 
Nashorn互換性モード
GraalVM JavaScriptは、Nashorn互換性モードに対応しています。Nashorn互換性に必要な機能の一部は、js.nashorn-compatオプションが有効になっている場合にのみ使用できます。これには、GraalVM JavaScriptでデフォルトで公開するのは望ましくないNashorn固有の拡張機能が当てはまります。
このフラグを使用するには、試験段階のオプションを有効にする必要があります。さらに、このフラグを設定すると、レガシーScriptEngineを操作する場合など、場合によってはGraalVM JavaScriptのデフォルトでのセキュリティ保護(secure by default)アプローチが無効になることがあります。
Nashorn互換性モードを使用する場合、デフォルトではECMAScript 5が互換性レベルとして設定されます。js.ecmascript-versionフラグを使用して別のECMAScriptバージョンを指定できます。これは完全なNashorn互換性と競合する可能性があることに注意してください。フラグを設定する方法を示すコード例は、この項の最後の方にあります。
js.nashorn-compatオプションは次の方法で設定できます: 1. コマンドライン・オプションを使用します:
js --experimental-options --js.nashorn-compat=true
2. ポリグロットAPIを使用します:
import org.graalvm.polyglot.Context;
try (Context context = Context.newBuilder().allowExperimentalOptions(true).option("js.nashorn-compat", "true").build()) {
    context.eval("js", "print(__LINE__)");
}
3. Javaアプリケーションの起動時にシステム・プロパティを使用します(アプリケーションのContext.BuilderでもallowExperimentalOptionsを有効にすることを忘れないでください):
java -Dpolyglot.js.nashorn-compat=true MyApplication
nashorn-compatフラグでのみ使用可能な機能は次のとおりです:
Java.isJavaFunction、Java.isJavaMethod、Java.isScriptObject、Java.isScriptFunctionnew Interface|AbstractClass(fn|obj)JavaImporterJSAdapter- 文字列値に対する
java.lang.Stringメソッド load("nashorn:parser.js")、load("nashorn:mozilla_compat.js")exit、quit
js.ecmascript-versionオプションは、同様に設定できます。これはサポートされているオプションであるため、ecmascript-versionを設定するだけのexperimental-optionsフラグを指定する必要はありません: 1.コマンドライン・オプションを使用します:
js --js.ecmascript-version=2020
Nashorn構文の拡張機能
Nashorn構文の拡張機能は、試験段階のオプションjs.syntax-extensionsを使用して有効にできます。また、Nashorn互換性モード(js.nashorn-compat)でもデフォルトで有効になっています。
GraalVM JavaScriptとNashornの違い
GraalVM JavaScriptは、いくつかの点でNashornとは異なります。相違点は意図的な設計上の決定によるものです。
デフォルトでのセキュリティ保護
GraalVM JavaScriptでは、デフォルトでのセキュリティ保護(secure by default)アプローチが使用されます。埋込み担当者が明示的に許可しないかぎり、JavaScriptコードでは、Javaクラスにアクセスできない、ファイル・システムにアクセスできない、といった制限があります。GraalVM JavaScriptのいくつかの機能(Nashorn互換性機能を含む)は、関連するセキュリティ設定が十分に許容範囲内にある場合にのみ使用できます。アプリケーションおよびホスト・システムに対するセキュアなデフォルト制限を解除するよう変更する場合は、必ずセキュリティ上の影響を理解してください。
使用可能な設定の完全なリストは、Context.Builderを参照してください。これらのフラグは、GraalVMポリグロットAPIを使用してコンテキストを構築するときに定義できます。
GraalVM JavaScriptの機能を有効にするために頻繁に必要なフラグは次のとおりです:
allowHostAccess(): ゲスト・アプリケーションからアクセス可能なパブリック・クラスのパブリック・コンストラクタ、メソッドまたはフィールドを構成します。アクセスを選択的に有効にするには、HostAccess.EXPLICITまたはカスタムHostAccessポリシーを使用します。無制限のアクセスを許可するには、HostAccess.ALLに設定します。allowHostClassLookup(): ゲスト・アプリケーションで参照できるJavaホスト・クラスを指定するフィルタを設定します。すべてのクラスのルックアップを許可するには、述語className -> trueに設定します。allowIO(): ゲスト言語がホスト・システムに対して無制限のIO操作(ファイル・システムからのload()に必要な操作など)を実行できるようにします。IOを有効にするには、trueに設定します。
レガシーScriptEngineでコードを実行する場合は、「Bindingsを介したオプションの設定」で設定方法を参照してください。
最後に、nashorn-compatモードでは、(Contextではなく) ScriptEngineでコードを実行するときに関連するフラグが有効になり、その設定ではNashornとの互換性が向上します。
ランチャ名js
GraalVM JavaScriptには、jsというバイナリ・ランチャが付属しています。ビルドの設定によっては、GraalVMにNashornとそのjjsランチャが引き続き付属している場合があることに注意してください。
ScriptEngine名graal.js
GraalVM JavaScriptには、ScriptEngineのサポートが付属しています。これは、graal.js、JavaScript、jsなどの複数の名前で登録されます。Nashornの完全な互換性が必要な場合は、必ず前述の説明に従ってNashorn互換性モードをアクティブ化してください。ビルドの設定によっては、GraalVMにNashornが引き続き付属し、ScriptEngineを介して提供される場合があります。詳細は、「ScriptEngine実装」を参照してください。
ClassFilter
GraalVM JavaScriptは、ポリグロットContextで起動する場合にクラス・フィルタをサポートしています。Context.Builder.hostClassFilterを参照してください。
完全修飾名
GraalVM JavaScriptでは、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');
非可逆式変換
GraalVM JavaScriptでは、Javaメソッドのコール時に引数の非可逆変換は許可されません。これにより、数値に関する検出しにくいバグが発生する可能性があります。
GraalVM JavaScriptでは、可能なかぎり狭く、損失なく変換できる引数型を持つオーバーロード・メソッドが常に選択されます。GraalVM JavaScriptでは、このようなオーバーロード・メソッドが使用できない場合、非可逆変換ではなく、TypeErrorがスローされます。通常、これは、実行されるオーバーロード・メソッドに影響します。
カスタムtargetTypeMappingを使用して、動作をカスタマイズできます。HostAccess.Builder#targetTypeMappingを参照してください。
ScriptObjectMirrorオブジェクト
GraalVM JavaScriptでは、クラス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]");
GraalVM JavaScriptでは、現在のContextへのアクセス権を持つJavaScriptアプリケーションからスレッドを作成することはできません。また、GraalVM JavaScriptでは、同時実行スレッドから同時に同じ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 GraalVM JavaScript
適切な同期が行われていれば、異なるスレッド間で複数のコンテキストを共有できます。複数のスレッドからGraalVM JavaScript Contextを使用するJavaアプリケーションの例は、ここを参照してください。
Nashorn互換性モードでのみ使用可能な拡張機能
Nashornで使用可能なJavaScriptの次の拡張機能は、GraalVM JavaScriptではデフォルトで非アクティブ化されています。これらは、GraalVMのNashorn互換性モードで提供されます。これらの機能に基づいて新しいアプリケーションを実装するのではなく、既存のアプリケーションをGraalVMに移行する手段として使用することを強くお薦めします。
文字列のlengthプロパティ
GraalVM JavaScriptでは、文字列のlengthプロパティが特別な方法で処理されるわけではありません。文字列の長さにアクセスする正規の方法は、lengthプロパティを読み取ることです:
myJavaString.length;
Nashornでは、ユーザーはプロパティと関数の両方としてlengthにアクセスできます。既存の関数コールlength()は、プロパティへのアクセスとして表す必要があります。Nashornの動作は、Nashorn互換性モードで模倣されます。
JavaScriptグローバル・オブジェクトのJavaパッケージ
GraalVM JavaScriptでは、完全修飾名のかわりにJava.typeを使用する必要があります。Nashorn互換性モードでは、Javaパッケージjava、javafx、javax、com、orgおよびeduがJavaScriptグローバル・オブジェクトに追加されます。
JavaImporter
JavaImporter機能は、Nashorn互換性モードでのみ使用できます。
JSAdapter
非標準JSAdapter機能の使用はお薦めしません。かわりに、同等の標準Proxy機能を使用してください。互換性確保のため、JSAdapterはNashorn互換性モードでも使用できます。
Java.*メソッド
Javaグローバル・オブジェクトに対してNashornで提供されているいくつかのメソッドは、Nashorn互換性モードでのみ使用でき、GraalVM JavaScriptでは現在サポートされていません。Nashorn互換性モードでは、Java.isJavaFunction、Java.isJavaMethod、Java.isScriptObjectおよびJava.isScriptFunctionを使用できます。Java.asJSONCompatibleは現在サポートされていません。
アクセサ
GraalVM JavaScriptのNashorn互換性モードでは、get、setまたはisを省略し、プロパティとして名前を使用することでgetterおよびsetterにアクセスできます:
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);
GraalVM JavaScriptでは、アクセスの順序についてNashornの動作が模倣されます:
- 読取り操作の場合、GraalVM JavaScriptでは最初に
getという名前のgetterとキャメル・ケースのプロパティ名のコールが試みられます。それが使用できない場合は、isという名前のgetterとキャメル・ケースのプロパティ名がコールされます。2つ目のケースでは、Nashornとは異なり、結果の値はブール型でなくても返されます。両方のメソッドが使用できない場合にのみ、プロパティ自体が読み取られます。 - 書込み操作の場合、GraalVM JavaScriptでは
setという名前のsetterとキャメル・ケースのプロパティ名のコールが試みられ、値が引数としてその関数に渡されます。setterが使用できない場合は、プロパティ自体が書き込まれます。 
Nashorn (およびGraalVM JavaScript)では、プロパティの読取り/書込みと関数コールが明確に区別されます。Javaクラスに同じ名前のフィールドとメソッドの両方が公開されている場合、obj.propertyでは常にフィールド(または前述のgetter)が読み取られますが、obj.property()では常にそれぞれのメソッドがコールされます。
その他の考慮事項
GraalVM JavaScriptの機能
GraalVM JavaScriptは、最新のECMAScript仕様の機能およびその拡張機能をサポートしています。「JavaScriptの互換性」を参照してください。この例では、これらの拡張機能を認識しない既存のソース・コードに干渉する可能性があるオブジェクトをグローバル・スコープに追加しています。
コンソール出力
GraalVM JavaScriptには、Nashornと互換性のあるprint組込み関数が用意されています。
GraalVM JavaScriptには、console.log関数も用意されています。これは純粋なJavaScriptモードではprintの別名ですが、ノード・モードでの実行時にはNode.jsによって提供される実装が使用されます。ノード・モードでconsole.logに対するJavaオブジェクト関連の動作が異なるのは、Node.jsにこのようなオブジェクトに対する特別な処理が実装されていないためです。