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.typeName
Java.from
、Java.to
Java.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.isScriptFunction
new Interface|AbstractClass(fn|obj)
JavaImporter
JSAdapter
- 文字列値に対する
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にこのようなオブジェクトに対する特別な処理が実装されていないためです。