2 Nashorn Java API
注意:
Nashornエンジン、jjs
ツール、およびjdk.scripting.nashornとjdk.scripting.nashorn.shellの各モジュールは、将来のリリースでの削除に備えてJDK 11では非推奨です。
この項には、Nashornエンジンの対話型言語シェル・モードで解釈されるスクリプト文の例が含まれています。この対話型シェルは、スクリプトを指定せずにjjs
コマンドを実行すると起動されます。これは何かを試す場合に便利ですが、Nashorn Java APIの主な目的は、JavaアプリケーションをNashornエンジンで解釈できるスクリプトとして作成することです。
Javaクラスへのアクセス
Nashornを使用してパッケージおよびクラスにアクセスするには、2つの方法があります。従来の方法ではPackages
グローバル・オブジェクトを使用しますが、推奨される方法ではJava
グローバル・オブジェクトを使用します。この項では、両方の方法について説明します。
事前に定義された最上位のPackages
オブジェクトでは、完全修飾名をPackages
オブジェクトのプロパティと同じように使用して、Javaパッケージおよびクラスにアクセスできます。次の例は、MyPackage.jar
がクラス・パスに含まれている場合に、MyPackage
パッケージとそのMyClass
クラスにアクセスする方法を示しています。
jjs> Packages.MyPackage
[JavaPackage MyPackage]
jjs> Packages.MyPackage.MyClass
[JavaClass MyPackage.MyClass]
Javaパッケージおよびクラスへのアクセスは、カスタム・パッケージおよびクラスへのアクセスより簡単です。参考までに、標準Javaパッケージ(com
、edu
、java
、javax
およびorg
)には、それぞれグローバル・オブジェクトが定義されています。これらは、Packages
オブジェクトのプロパティに対応する別名を持っています。次の例は、java.lang
パッケージとjava.lang.System
クラスにアクセスする方法を示しています。
jjs> java.lang
[JavaPackage java.lang]
jjs> typeof java.lang
object
jjs> java.lang.System
[JavaClass java.lang.System]
jjs> typeof java.lang.System
function
前の例からわかるように、NashornはJavaパッケージをJavaPackage
オブジェクトとして解釈し、Javaクラスを(そのクラスのコンストラクタとして使用できる) JavaClass
関数オブジェクトとして解釈します。「Javaオブジェクトの作成」でクラスをインスタンス化する方法について説明します。
従来の方法によるJavaパッケージおよびクラスへのアクセスは、直感的で簡単ですが、同時に次の理由で不十分かつ限定的であり、エラーになりやすい傾向があります。
-
各プロパティのアクセスにコストがかかるため、深い階層のパッケージやクラスへのアクセスが遅くなる可能性があります。
-
Java配列を作成するための特別な構文がありません。回避策として、
java.lang.reflect.Array
クラスを使用する必要があります。 -
クラス名のスペルを間違えると、Nashornはパッケージ名が指定されたと見なし、それを
JavaClass
関数オブジェクトではなくJavaPackage
オブジェクトとして解釈します。それをクラスとして使用しようとしたときにエラーがスローされるまで、このことに気付かない可能性があります。これを回避するには、typeof
演算子を使用して、アクセスしようとしている構造が関数オブジェクトとして解釈される条件をテストします。次の例は、この条件チェックの動作を示しています。jjs> typeof java.lang.System == "function" true jjs> typeof java.lang.Zyztem == "function" false
前述の方法の短所を回避するため、NashornにはJavaクラスを操作する関数を持つJava
グローバル・オブジェクトが定義されています。Java.type()
関数は、Javaクラスの完全修飾名を含む文字列を取り、対応するJavaClass
関数オブジェクトを返します。次の例は、java.lang.System
クラスにアクセスする方法を示しています。
jjs> Java.type("java.lang.System")
[JavaClass java.lang.System]
Javaにクラスをインポートする場合と同じように、スクリプトの先頭でJavaClass
型の変数を宣言することをお薦めします。次の例は、System
変数を宣言し、それにjava.lang.System
クラスの値を代入する方法を示しています。
jjs> var System = Java.type("java.lang.System")
jjs> System
[JavaClass java.lang.System]
Javaオブジェクトの作成
クラスをインスタンス化するには、JavaClass
関数オブジェクトをnew
演算子に渡します。Nashornは、関数に渡された引数に基づいて、対応するコンストラクタを呼び出します。
次の例は、デフォルトの初期容量を使用した場合と初期容量を100に設定した場合の、java.util.HashMap
クラスをインスタンス化する方法を示しています。
jjs> var HashMap = Java.type("java.util.HashMap")
jjs> var mapDef = new HashMap()
jjs> var map100 = new HashMap(100)
クラスおよびインスタンス・メンバーへのアクセス
次のように、標準ドット表記を使用してstaticフィールド、メソッドおよび内部クラスにアクセスできます。
jjs> Java.type("java.lang.Math").PI
3.141592653589793
jjs> Java.type("java.lang.System").currentTimeMillis()
1375813353330
jjs> Java.type("java.util.Map").Entry
[JavaClass java.util.Map$Entry]
内部クラスには、ドル記号($
)または(Javaと一貫性がある)ドットを区切り文字とする内部表現を使用してアクセスすることもできます。
jjs> Java.type("java.util.Map$Entry")
[JavaClass java.util.Map$Entry]
jjs> Java.type("java.util.Map.Entry")
[JavaClass java.util.Map$Entry]
オブジェクトのインスタンス・メソッドを呼び出したり、インスタンス・フィールドにアクセスしたりするには、Javaの場合と同じようにドット演算子を使用します。次の例は、String
オブジェクトのtoUpperCase()
メソッドを呼び出す方法を示しています。
jjs> var String = Java.type("java.lang.String")
jjs> var str = new String("Hello")
jjs> str
Hello
jjs> var upper = str.toUpperCase()
jjs> upper
HELLO
Nashornは、角カッコを使ったメンバー・アクセスもサポートします。その場合は、メンバーの名前を角カッコ([]
)で囲まれた文字列として指定し、その直後にクラス(staticメンバーの場合)またはオブジェクト(インスタンス・メンバーの場合)を指定します。この方法は、ECMAScriptでドット表記の代替として定義されており、Java開発者にとって直感的ではありません。しかし、これを使用してメソッドのオーバーロードのあいまいさを解決できます。デフォルトでは、Nashornは引数にもっとも一致するオーバーロード・メソッドを使用しますが、これが期待する動作であるとはかぎりません。たとえば、次の例に示すように、double
値を出力する場合は、java.lang.System.out.println(double)
メソッド・オーバーロードを使用する必要があります。
jjs> Java.type("java.lang.System").out.println(10)
10
jjs> Java.type("java.lang.System").out["println(double)"](10)
10.0
JavaBeansの使用
Nashornでは、JavaBeansのアクセサおよびミューテータ・メソッドを同等のJavaScriptプロパティとして扱うことができます。プロパティの名前は、JavaBeanメソッドの名前から接尾辞のget
またはset
を除き、先頭を小文字にしたものです。
たとえば、java.util.Date
オブジェクトのgetYear()
およびsetYear()
メソッドを呼び出すには、次のようにyear
プロパティを使用します。
jjs> var Date = Java.type("java.util.Date")
jjs> var date = new Date()
jjs> date.year + 1900
2013
jjs> date.year = 2014 - 1900
114
jjs> date.year + 1900
2014
Java配列の操作
Java配列クラスにアクセスするには、配列とそれに続く角カッコのペアからなる(Java構文と同様の)オブジェクトの型をJava.type()
関数に渡します。
次の例は、整数のJava配列とString
オブジェクトのJava配列にアクセスする方法を示しています。
jjs> Java.type("int[]")
[JavaClass [I]
jjs> Java.type("java.lang.String[]")
[JavaClass [Ljava.lang.String;]
配列型オブジェクトを作成した後は、それを使用して他のクラスと同じように配列をインスタンス化できます。次の例に示すように、インデックスを使って配列のエントリにアクセスしたり、ドット表記または(Java構文と同じように)角カッコ表記を使ってメンバーにアクセスしたりできます。
jjs> var IntArrayType = Java.type("int[]")
jjs> var arr = new IntArrayType(10)
jjs> arr[1] = 123
123
jjs> arr[2] = 321
321
jjs> arr[1] + arr[2]
444
jjs> arr[10]
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 10
jjs> arr.length
10
既存のJavaScript配列がある場合は、Java.to()
関数を使用してそれをJava配列に変換できます。次の例は、文字列"a"
、"b"
および"c"
のJavaScript配列を、同じ値を持つjava.lang.String[]
配列に変換する方法を示しています。
jjs> var jsArr = ["a","b","c"]
jjs> var strArrType = Java.type("java.lang.String[]")
jjs> var javaArr = Java.to(jsArr, strArrType)
jjs> javaArr.class
class [Ljava.lang.String;
jjs> javaArr[0]
a
次のように、for
およびfor each
文を使用して、Java配列のインデックスと値を反復処理できます。
jjs> for (var i in javaArr) print(i)
0
1
2
jjs> for each (var i in javaArr) print(i)
a
b
c
Java文字列の操作
Nashornは文字列をjava.lang.String
オブジェクトとして表現します。2つの文字列を連結すると、String
インスタンスを取得します。
jjs> var a = "abc"
jjs> a.class
class java.lang.String
jjs> var b = a + "def"
jjs> b.class
class java.lang.String
jjs> var c = String(b)
jjs> c.class
class java.lang.String
Java数値の操作
Nashornは、数値を、実行される計算に応じてjava.lang.Double
、java.lang.Long
またはjava.lang.Integer
オブジェクトとして解釈します。次の例に示すように、Number()
関数を使用して数値を強制的にDouble
オブジェクトにすることができます。
jjs> var intNum = 10
jjs> intNum.class
class java.lang.Integer
jjs> var dblNum = Number(intNum)
jjs> dblNum.class
class java.lang.Double
Javaリストおよびマップの使用
NashornはJavaリストを配列として解釈し、for each
文でリストの値を反復処理します。マップでキーおよび値を反復処理するには、keySet()
およびvalues()
メソッドを使用します。
次の例に示すように、角カッコ([]
)内のインデックスを使用してリストの要素にアクセスしたり、for each
文を使用してリストの値を反復処理したりできます。
jjs> var ArrayList = Java.type("java.util.ArrayList")
jjs> var alist = new ArrayList()
jjs> alist.add("a")
true
jjs> alist.add("b")
true
jjs> alist.add("c")
true
jjs> alist[1]
b
jjs> for each (var i in alist) print(i)
a
b
c
注意:
角カッコ内のインデックスを使用してリストおよびマップ要素にアクセスできますが、この構文を使用してキューおよび設定要素にはアクセスできません。次の例は、HashMap
オブジェクトを作成し、keySet()
およびvalues()
メソッドを使用してそのキーと値を反復処理する方法を示しています。
jjs> var HashMap = Java.type("java.util.HashMap")
jjs> var hm = new HashMap()
jjs> hm.put("name", "Bob")
jjs> hm.put("age", 40)
jjs> hm.put("weight", 180)
jjs> for each (var i in hm.keySet()) print(i)
weight
age
name
jjs> for each (var i in hm.values()) print(i)
180
40
Bob
または、次の例に示すように、リストの値の反復処理と同様に、マップの値を反復処理することもできます。
jjs> for each (var i in hm) print(i)
180
40
Bob
Javaクラスの拡張
クラスを拡張するには、Java.extend()
関数を使用します。この関数は、最初の引数としてJava型を取り、他の引数として(JavaScript関数形式の)メソッド実装を取ります。
次のスクリプトは、java.lang.Runnable
インタフェースを拡張し、それを使用して新しいjava.lang.Thread
オブジェクトを構築しています。
var Run = Java.type("java.lang.Runnable");
var MyRun = Java.extend(Run, {
run: function() {
print("Run in separate thread");
}
});
var Thread = Java.type("java.lang.Thread");
var th = new Thread(new MyRun());
メソッドを実装するための関数をコンストラクタの引数として指定すると、Nashornは自動的に機能インタフェース(注釈タイプFunctionalInterfaceに関する項を参照)を拡張できます。次のスクリプトは、java.lang.Runnable
インタフェースを拡張し、それを使用して新しいjava.lang.Thread
オブジェクトを構築するスクリプトを示していますが、Java.extend()
関数が機能インタフェースに対して自動的に呼び出されているため、例2-1より使用するコードの行数が少なくなっています。
var Thread = Java.type("java.lang.Thread")
var th = new Thread(function() print("Run in a separate thread"))
『Java Platform, Standard Edition Java Scriptingプログラマ・ガイド』のJavaクラスの拡張に関する項では、Java.extend()
関数の機能について説明しています。
例2-1 Javaクラスの拡張
var Run = Java.type("java.lang.Runnable");
var MyRun = Java.extend(Run, {
run: function() {
print("Run in separate thread");
}
});
var Thread = Java.type("java.lang.Thread");
var th = new Thread(new MyRun());
例2-2 機能インタフェースの拡張
var Thread = Java.type("java.lang.Thread")
var th = new Thread(function() print("Run in a separate thread"))
指定したJavaクラスへのスクリプト・アクセスの制限
jdk.nashorn.api.scripting.ClassFilter
インタフェースにより、Nashornスクリプト・エンジンで実行されるスクリプトから指定したJavaクラスへのアクセスを制限することで、JavaScriptコードからJavaクラスへのアクセスに対してきめの細かい制御を提供できます。
Nashornの、とりわけサーバー側JavaScriptフレームワークを埋め込んでいるアプリケーションは、信頼できないソースからスクリプトを実行しなければならないことがよくあり、そのためアクセスをJava APIに制限する必要があります。これらのアプリケーションは、ClassFilterインタフェースを実装して、Javaクラス・アクセスをJavaクラスのサブセットに制限できます。それには、クライアント・アプリケーションでNashorn APIを使用して、Nashornスクリプト・エンジンをインスタンス化する必要があります。
注意:
ClassFilterインタフェースでJavaクラスへのアクセスを防止できますが、信頼できないスクリプトをセキュアに実行するには不十分です。ClassFilterインタフェースはセキュリティ・マネージャのかわりにはなりません。信頼できないソースのスクリプトを評価する前に、セキュリティ・マネージャを使用してアプリケーションを実行する必要があります。クラス・フィルタは、セキュリティ・マネージャよりもきめの細かい制御を提供します。たとえば、Nashornを埋め込んだアプリケーションは、セキュリティ・マネージャが許可する可能性のあるスクリプト、またはリソースが集中する他の操作からスレッドが起動するのを防ぐことができます。クラス・フィルタの使用
クラス・フィルタを使用するには、ClassFilter
インタフェースを実装して、メソッドboolean exposeToScripts(String)
を定義します。
String
引数は、Nashornスクリプト・エンジンがスクリプトの実行時に遭遇するJavaクラスまたはパッケージの名前です。メソッドexposeToScripts
を、スクリプトがアクセスしないようにするクラスとパッケージでfalse
を戻すように定義します。続いて、メソッドNashornScriptEngineFactory.getScriptEngine(ClassFilter)
またはNashornScriptEngineFactory.getScriptEngine(String[]、ClassLoader、ClassFilter)
を使用したクラス・フィルタを持つNashronスクリプト・エンジンを作成します。
JavaリフレクションAPIへのアクセスの制限
セキュリティ・マネージャを使用している場合、Nashornでは、スクリプトは、nashorn.javaReflection
実行時権限を持っているときのみJavaリフレクションAPI(たとえば、java.lang.reflect
とjava.lang.invoke
パッケージ)を使用することができます。
クラス・フィルタを使用している場合、セキュリティ・マネージャがない場合でも、NashornではJavaリフレクションAPIにアクセスできません。JavaリフレクションAPIが利用できる場合、スクリプトがClass.forName(String)
を使用してクラス・フィルタを回避できるため、クラス・フィルタを使用する必要はない点に注意してください。
ClassFilterインタフェースの使用例
次の例のMyClassFilterTest.java
に、ClassFilter
インタフェースを示します。スクリプトがクラスjava.io.Fileにアクセスするのを制限しています。
import javax.script.ScriptEngine; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; public class MyClassFilterTest { class MyCF implements ClassFilter { @Override public boolean exposeToScripts(String s) { if (s.compareTo("java.io.File") == 0) return false; return true; } } public void testClassFilter() { final String script = "print(java.lang.System.getProperty(\"java.home\"));" + "print(\"Create file variable\");" + "var File = Java.type(\"java.io.File\");"; NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine engine = factory.getScriptEngine( new MyClassFilterTest.MyCF()); try { engine.eval(script); } catch (Exception e) { System.out.println("Exception caught: " + e.toString()); } } public static void main(String[] args) { MyClassFilterTest myApp = new MyClassFilterTest(); myApp.testClassFilter(); } }
この例の出力は次のとおりです。
C:\Java\jdk-11 Create file variable Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException: java.io.File
MyClassFilterTest.java
の例では、次の操作を実行しています。