ホームページ >Java >&#&チュートリアル >Java8用Nashornスクリプトエンジンの詳細説明

Java8用Nashornスクリプトエンジンの詳細説明

黄舟
黄舟オリジナル
2017-03-20 10:20:342376ブラウズ

この記事は、Nashorn JavaScript エンジンのわかりやすいコード例をすべて理解することを目的としています。 Nashorn JavaScript エンジンは Java SE 8 の一部であり、Google V8 (Google の Chrome や Node.js の背後にあるエンジン) などの他のスタンドアロン エンジンと競合します。 Nashorn は、JVM 上で動的 JavaScript スクリプトを実行する Java の機能を拡張します。

次の 15 分ほどで、JVM 上で JavaScript を動的に実行する方法を学びます。 いくつかの短いコード例を使用して、最近の Nashorn 言語の機能を示します。 Java と JavaScript が相互に呼び出す方法を学びます。最後に、動的スクリプトを日常の Java ビジネスに統合する方法についても説明します。

Nashorn を使用する

Nashorn JavaScript エンジンは、プログラミング の方法で Java プログラムで使用されるか、ディレクトリ $JAVA_HOME/bin にあるコマンドライン ツール jjs で使用されます。 jjs へのシンボリックリンクを作成する場合は、次のようにします: $JAVA_HOME/bin中。如果你准备建立一个jjs的符号链接,如下:

$ cd /usr/bin
$ ln -s $JAVA_HOME/bin/jjs jjs
$ jjs
jjs> print('Hello World');

本教程关注的是在java代码中使用 nashorn ,所以我们现在跳过jjs。用java代码来一个简单的  HelloWorld示例,如下:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello World!');");

为了在java中执行JavaScript代码,首先使用原先Rhino (旧版Java中来自Mozilla的引擎)中的包javax.script来创建一个nashorn脚本引擎。.

既可以向上面那样把JavaScript代码作为一个字符串来直接执行,也可放入一个js脚本文件中,如:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));

Nashorn javascript是基于 ECMAScript 5.1 ,但nashorn后续版本将支持 ECMAScript 6:

当前Nashorn的策略是遵循ECMAScript规范。 当我们发布JDK 8时,我们将实现ECMAScript 5.1标准。后续的 Nashorn的版本将实现 ECMAScript Edition 6标准。

Nashorn定义了很多语言和扩展了 ECMAScript标准的API 。接下来我们看看java与JavaScript的通信。

Java调用Javascript 函数

Nashorn 支持java代码直接调用定义在脚本文件中JavaScript函数。你可以把java对象作为函数的参数且在调用函数的java方法中接收返回的数据。

如下的JavaScript代码将会在java端调用:

var fun1 = function(name) {
    print('Hi there from Javascript, ' + name);
    return "greetings from javascript";
};

var fun2 = function (object) {
    print("JS Class Definition: " + Object.prototype.toString.call(object));
};

为了调用函数,你首先得把脚本引擎转换为 Invocable。NashornScriptEngine 实现了 Invocable 接口且定义一个调用JavaScript函数的方法 invokeFunction ,传入函数名即可。

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));

Invocable invocable = (Invocable) engine;

Object result = invocable.invokeFunction("fun1", "Peter Parker");
System.out.println(result);
System.out.println(result.getClass());

// Hi there from Javascript, Peter Parker
// greetings from javascript
// class java.lang.String

上述代码的执行将在控制台打印三行信息。调用 print 函数将输出内容通过管道送到 System.out 控制台,因此我们首先看到的是 JavaScript打印的信息。

现在我们通过传递任意的 Java 对象去调用第二个函数:

invocable.invokeFunction("fun2", new Date());
// [object java.util.Date]

invocable.invokeFunction("fun2", LocalDateTime.now());
// [object java.time.LocalDateTime]

invocable.invokeFunction("fun2", new Person());
// [object com.winterbe.java8.Person]

你可以传递任意 Java 对象而不会在 JavaScript 这边丢失类型信息。因为脚本本身是在 JVM 虚拟机中执行的,我们可以完全利用 nashorn 引擎的 Java API 和外部库的强大功能。

在 JavaScript 端调用 Java 方法

在 JavaScript 中调用 Java 方法很简单。首先我们定义一个静态的 Java 方法:

static String fun1(String name) {
    System.out.format("Hi there from Java, %s", name);
    return "greetings from java";
}

JavaScript 可通过 Java.type API 来引用 Java 类。这跟在 Java 类中引入其他类是类似的。当定义了 Java 类型后我们可直接调用其静态方法 fun1() 并打印结果到 sout。因为方法是静态的,所以我们无需创建类实例。

var MyJavaClass = Java.type('my.package.MyJavaClass');

var result = MyJavaClass.fun1('John Doe');
print(result);

// Hi there from Java, John Doe
// greetings from java

当调用java 方法时,Nashorn怎样处理原生JavaScript类型与java类型转换?让我们用一个简单的例子来发现。

下面的java方法简单打印实际的类方法参数的类型:

static void fun2(Object object) {
    System.out.println(object.getClass());
}

为了解引擎如何处理类型转换,我使用不同JavaScript类型来调用java方法:

MyJavaClass.fun2(123);
// class java.lang.Integer

MyJavaClass.fun2(49.99);
// class java.lang.Double

MyJavaClass.fun2(true);
// class java.lang.Boolean

MyJavaClass.fun2("hi there")
// class java.lang.String

MyJavaClass.fun2(new Number(23));
// class jdk.nashorn.internal.objects.NativeNumber

MyJavaClass.fun2(new Date());
// class jdk.nashorn.internal.objects.NativeDate

MyJavaClass.fun2(new RegExp());
// class jdk.nashorn.internal.objects.NativeRegExp

MyJavaClass.fun2({foo: 'bar'});
// class jdk.nashorn.internal.scripts.JO4

原始的javascript 类型被转换为适当的 java 包装器类。而不是本地javascript对象内部适配器类。请记住,这些类来自于jdk.nashorn.internal

static void fun3(ScriptObjectMirror mirror) {
    System.out.println(mirror.getClassName() + ": " +
        Arrays.toString(mirror.getOwnKeys(true)));
}
このチュートリアルは Java コードでの nashorn の使用に焦点を当てているため、今のところ jjs はスキップします。 Java コードを使用した簡単な HelloWorld の例は次のとおりです:
MyJavaClass.fun3({
    foo: 'bar',
    bar: 'foo'
});

// Object: [foo, bar]

Java で JavaScript コードを実行するには、まずオリジナルの Rhino (古いバージョンの Java での Mozilla のエンジン) のパッケージ javax.script を使用して nashorn を作成します。スクリプトエンジン。

JavaScript コードを 文字列🎜 として直接実行することも、js に含めることもできます。次のようなスクリプト ファイル: 🎜
function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.getFullName = function() {
        return this.firstName + " " + this.lastName;
    }
}
🎜Nashorn JavaScript は ECMAScript 5.1 に基づいていますが、Nashorn の後続のバージョンは ECMAScript 6 をサポートします: 🎜🎜 現在の Nashorn の戦略は ECMAScript 仕様に従うことです。 JDK 8 をリリースすると、ECMAScript 5.1 標準が実装されます。 Nashorn の後続のバージョンでは、ECMAScript Edition 6 標準が実装されます。 🎜🎜Nashorn は多くの言語を定義し、ECMAScript 標準
API🎜 を拡張しています。次に、Java と JavaScript の間の通信を見ていきます。 🎜🎜Java は Javascript 関数を呼び出します🎜🎜Nashorn は、スクリプト ファイルで定義された JavaScript 関数を直接呼び出す Java コードをサポートしています。 Java オブジェクトをパラメータとして関数に渡し、関数を呼び出す Java メソッドで返されたデータを受け取ることができます。 🎜🎜次の JavaScript コードが Java 側で呼び出されます: 🎜
static void fun4(ScriptObjectMirror person) {
    System.out.println("Full Name is: " + person.callMember("getFullName"));
}
🎜 関数を呼び出すには、まずスクリプト エンジンを Invocable に変換する必要があります。 NashornScriptEngine は、呼び出し可能な
インターフェイス🎜 を実装し、JavaScript 関数 invokeFunction 、関数名を渡すだけです。 🎜<pre class="brush:java;toolbar:false;">var person1 = new Person(&quot;Peter&quot;, &quot;Parker&quot;); MyJavaClass.fun4(person1); // Full Name is: Peter Parker</pre>🎜上記のコードを実行すると、コンソールに 3 行の情報が表示されます。 print 関数を呼び出すと出力が System.out コンソールにパイプされるため、最初に表示されるのは JavaScript によって出力された情報です。 🎜🎜ここで、任意の Java オブジェクトを渡して 2 番目の関数を呼び出します。🎜<pre class="brush:java;toolbar:false;">var IntArray = Java.type(&quot;int[]&quot;); var array = new IntArray(5); array[0] = 5; array[1] = 4; array[2] = 3; array[3] = 2; array[4] = 1; try { array[5] = 23; } catch (e) { print(e.message); // Array index out of range: 5 } array[0] = &quot;17&quot;; print(array[0]); // 17 array[0] = &quot;wrong type&quot;; print(array[0]); // 0 array[0] = &quot;17.3&quot;; print(array[0]); // 17</pre>🎜JavaScript 側の型情報を失うことなく、任意の Java オブジェクトを渡すことができます。スクリプト自体は JVM 仮想マシンで実行されるため、nashorn エンジンの Java API と外部ライブラリの機能を最大限に活用できます。 🎜🎜JavaScript 側での Java メソッドの呼び出し🎜🎜 JavaScript での Java メソッドの呼び出しは簡単です。まず、<a href="http://www.php.cn/wiki/188.html" target="_blank">static🎜Java メソッドを定義します:🎜<pre class="brush:java;toolbar:false;">var ArrayList = Java.type(&amp;#39;java.util.ArrayList&amp;#39;); var list = new ArrayList(); list.add(&amp;#39;a&amp;#39;); list.add(&amp;#39;b&amp;#39;); list.add(&amp;#39;c&amp;#39;); for each (var el in list) print(el); // a, b, c</pre>🎜JavaScript には Java.type API を通じてアクセスできます</a><a href="http://www.php.cn/wiki/231.html" target="_blank">引用 🎜 Java クラス。これは、Java クラスに他のクラスを導入するのと似ています。 Java 型を定義した後、その静的メソッド fun1() を直接呼び出して、結果を soout に出力できます。メソッドは静的であるため、クラスのインスタンスを作成する必要はありません。 🎜<pre class="brush:java;toolbar:false;">var map = new java.util.HashMap(); map.put(&amp;#39;foo&amp;#39;, &amp;#39;val1&amp;#39;); map.put(&amp;#39;bar&amp;#39;, &amp;#39;val2&amp;#39;); for each (var e in map.keySet()) print(e); // foo, bar for each (var e in map.values()) print(e); // val1, val2</pre>🎜 Java メソッドを呼び出すときに、Nashorn はネイティブ JavaScript 型と java</a><a href="http://www.php.cn/php/php-tp-conversion.html" target="_blank">型変換をどのように処理しますか 🎜 ?簡単な例で見てみましょう。 🎜🎜次の Java メソッドは、実際のクラス メソッドのパラメーターの型を単純に出力します: 🎜<pre class="brush:java;toolbar:false;">var list2 = new java.util.ArrayList(); list2.add(&quot;ddd2&quot;); list2.add(&quot;aaa2&quot;); list2.add(&quot;bbb1&quot;); list2.add(&quot;aaa1&quot;); list2.add(&quot;bbb3&quot;); list2.add(&quot;ccc&quot;); list2.add(&quot;bbb2&quot;); list2.add(&quot;ddd1&quot;); list2 .stream() .filter(function(el) { return el.startsWith(&quot;aaa&quot;); }) .sorted() .forEach(function(el) { print(el); }); // aaa1, aaa2</pre>🎜 エンジンが型変換を処理する方法を理解するために、さまざまな JavaScript 型を使用して Java メソッドを呼び出しました: 🎜<pre class="brush: java; gutter: true; first-line: 1">var Runnable = Java.type(&amp;#39;java.lang.Runnable&amp;#39;); var Printer = Java.extend(Runnable, { run: function() { print(&amp;#39;printed from a separate thread&amp;#39;); } }); var Thread = Java.type(&amp;#39;java.lang.Thread&amp;#39;); new Thread(new Printer()).start(); new Thread(function() { print(&amp;#39;printed from another thread&amp;#39;); }).start(); // printed from a separate thread // printed from another thread</pre>🎜 元の JavaScript 型は適切な型に変換されました。 Javaラッパークラス。ローカル</a><a href="http://www.php.cn/js/js-jsref-tutorial.html" target="_blank">JavaScript オブジェクト🎜内部アダプター クラスの代わりに。これらのクラスは <code>jdk.nashorn.internal から取得されているため、🎜クライアントではこれらのクラスを使用しないでください。🎜🎜internal とマークされたものはすべて、下から変更される可能性があります。🎜

ScriptObjectMirror

当使用ScriptObjectMirror把本地JavaScript对象传入时,实际上是有一个java对象表示JavaScript 对象。 ScriptObjectMirror 实现了接口与jdk.nashorn.api内部的映射。这个包下的类目的就是用于客户端代码使用。

下一个示例更改参数类型Object为ScriptObjectMirror,因此我们能获取到传入JavaScript中对象的一些信息:

static void fun3(ScriptObjectMirror mirror) {
    System.out.println(mirror.getClassName() + ": " +
        Arrays.toString(mirror.getOwnKeys(true)));
}

当我们把传递对象hash到方法中,在Java端就能访问这些属性

MyJavaClass.fun3({
    foo: &#39;bar&#39;,
    bar: &#39;foo&#39;
});

// Object: [foo, bar]

我们也可以在Java端调用JavaScript对象中的函数。我们首先定义一个JavaScript类型 Person,包含属性 firstName 、lastName 和函数getFullName。

function Person(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.getFullName = function() {
        return this.firstName + " " + this.lastName;
    }
}

javascript 函数getFullName 能被 ScriptObjectMirror 的callMember()调用。

static void fun4(ScriptObjectMirror person) {
    System.out.println("Full Name is: " + person.callMember("getFullName"));
}

当我们传入一个新的person给java 方法时,我们能在控制台看到预期结果:

var person1 = new Person("Peter", "Parker");
MyJavaClass.fun4(person1);

// Full Name is: Peter Parker

语言扩展

Nashorn 定义一系列的语言和扩展了 ECMAScript 标准的API。 让我们直接进入最新的功能:

类型数组

原始javascript 数组时无类型的。 Nashorn 运行你在JavaScript中使用java数组:

var IntArray = Java.type("int[]");

var array = new IntArray(5);
array[0] = 5;
array[1] = 4;
array[2] = 3;
array[3] = 2;
array[4] = 1;

try {
    array[5] = 23;
} catch (e) {
    print(e.message);  // Array index out of range: 5
}

array[0] = "17";
print(array[0]);  // 17

array[0] = "wrong type";
print(array[0]);  // 0

array[0] = "17.3";
print(array[0]);  // 17

int[] 数组的行为像一个真正的 java int 数组。 但当我们试图添加非整数的值的数组时,Nashorn 会执行隐式类型转换。 字符串会自动转换为int,这相当方便。

集合与For Each

我们可以使用java的集合来代替数组。首先定义使用 Java.type定义一个java类型,而后根据需要创建一个实例。

var ArrayList = Java.type(&#39;java.util.ArrayList&#39;);
var list = new ArrayList();
list.add(&#39;a&#39;);
list.add(&#39;b&#39;);
list.add(&#39;c&#39;);

for each (var el in list) print(el);  // a, b, c

为了遍历集合和数组中的元素,Nashorn 引入了 for each 语句。这就像是 Java 的 for 循环一样。

这里是一个对集合元素进行遍历的例子,使用的是 :

var map = new java.util.HashMap();
map.put(&#39;foo&#39;, &#39;val1&#39;);
map.put(&#39;bar&#39;, &#39;val2&#39;);

for each (var e in map.keySet()) print(e);  // foo, bar

for each (var e in map.values()) print(e);  // val1, val2

Lambda 表达式和 Streams

似乎大家都比较喜欢 Lambda 和 Streams —— Nashorn 也是!虽然 ECMAScript 5.1 中缺少 Java 8 Lambda 表达式中的紧缩箭头的语法,但我们可以在接受 Lambda 表达式的地方使用函数来替代。

var list2 = new java.util.ArrayList();
list2.add("ddd2");
list2.add("aaa2");
list2.add("bbb1");
list2.add("aaa1");
list2.add("bbb3");
list2.add("ccc");
list2.add("bbb2");
list2.add("ddd1");

list2
    .stream()
    .filter(function(el) {
        return el.startsWith("aaa");
    })
    .sorted()
    .forEach(function(el) {
        print(el);
    });
    // aaa1, aaa2

扩展类

Java 的类型可以简单的通过 Java.extend 进行扩展,在下个例子你将在脚本中创建一个多线程示例:

var Runnable = Java.type(&#39;java.lang.Runnable&#39;);
var Printer = Java.extend(Runnable, {
    run: function() {
        print(&#39;printed from a separate thread&#39;);
    }
});

var Thread = Java.type(&#39;java.lang.Thread&#39;);
new Thread(new Printer()).start();

new Thread(function() {
    print(&#39;printed from another thread&#39;);
}).start();

// printed from a separate thread
// printed from another thread

参数重载

方法和函数可以使用点符号或方括号来进行调用。

var System = Java.type(&#39;java.lang.System&#39;);
System.out.println(10);              // 10
System.out["println"](11.0);         // 11.0
System.out["println(double)"](12);   // 12.0

在使用重载的参数来调用方法时可以传递可选参数来确定具体调用了哪个方法,如 println(double)。

Java Beans

我们不需要常规的用 getter 或者 setter 来访问类成员属性,可直接用属性名简单访问 Java Bean 中的属性。例如:

var Date = Java.type(&#39;java.util.Date&#39;);
var date = new Date();
date.year += 1900;
print(date.year);  // 2014

函数语法

如果只是简单的一行函数我们可以不用大括号:

function sqr(x) x * x;
print(sqr(3));    // 9

属性绑定

来自不同对象的属性可以绑定在一起:

var o1 = {};
var o2 = { foo: &#39;bar&#39;};

Object.bindProperties(o1, o2);

print(o1.foo);    // bar
o1.foo = &#39;BAM&#39;;
print(o2.foo);    // BAM

字符串处理

我喜欢字符串裁剪.

print("   hehe".trimLeft());            // hehe
print("hehe    ".trimRight() + "he");   // hehehe

在哪里

以防忘记你在哪里:

print(FILE, LINE, DIR);

Import 的范围

有时,这在一次性导入多个java 包时非常有用。我们可以使用JavaImporter并结合with,在with块范围内引用:

var imports = new JavaImporter(java.io, java.lang);
with (imports) {
    var file = new File(FILE);
    System.out.println(file.getAbsolutePath());
    // /path/to/my/script.js
}

数组转换

有些包时可以直接使用而不必利用 Java.type 或JavaImporter引入,如 java.util:

var list = new java.util.ArrayList();
list.add("s1");
list.add("s2");
list.add("s3");

如下的代码演示了将java list转换为JavaScript的数组:

var jsArray = Java.from(list);
print(jsArray);                                  // s1,s2,s3
print(Object.prototype.toString.call(jsArray));  // [object Array]

其他的方式:

var javaArray = Java.to([3, 5, 7, 11], "int[]");

调用父类函数

在 JavaScript 中访问重载的成员会有一点点尴尬,因为 ECMAScript 没有类似 Java 的 super 关键字一样的东西。所幸的是 Nashorn 有办法解决。

首先我们在 Java 代码中定义一个超类:

class SuperRunner implements Runnable {
    @Override
    public void run() {
        System.out.println("super run");
    }
}

接下来我们在 JavaScript 中重载 SuperRunner 。创建一个新的 Runner 实例时请注意 Nashorn 的扩展语法:其重载成员的语法是参考 Java 的匿名对象的做法。

var SuperRunner = Java.type(&#39;com.winterbe.java8.SuperRunner&#39;);
var Runner = Java.extend(SuperRunner);

var runner = new Runner() {
    run: function() {
        Java.super(runner).run();
        print(&#39;on my run&#39;);
    }
}
runner.run();

// super run
// on my run

我们使用Java.super调用了重载方法 SuperRunner.run()。

在JavaScript中执行其它脚本是十分容易的。我们可以load函数载入本地或远程的脚本。

在我的很多web前端中都使用了 Underscore.js ,因此在Nashorn中我们可以重用 Underscore:

load(&#39;http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js&#39;);

var odds = _.filter([1, 2, 3, 4, 5, 6], function (num) {
    return num % 2 == 1;
});

print(odds);  // 1, 3, 5

扩展脚本的执行是在同一个 JavaScript 上下文中,因此我们可以直接访问 underscore 变量。记住脚本的加载可能会因为变量名的重叠导致代码出问题。

我们可以通过将加载的脚本文件放置到一个新的全局上下文来解决这个问题:

loadWithNewGlobal(&#39;script.js&#39;);

命令行脚本

如果你对用 Java 编写命令行脚本很感兴趣的话,可以试试 Nake 。Nake 是一个为 Java 8 Nashorn 准备的简单 Make 工具。你可以在 Nakefile 文件中定义任务,然后使用 nake — myTask 来运行任务。任务使用 JavaScript 编写并通过 Nashorn 脚本模式运行,因此你可以让你的终端应用完全利用 Java 8 API 和其他 Java 库强大的功能。

对 Java 开发者而言,编写命令行脚本从来没有如此简单过。

总结

我希望这篇文章对你有用,可以让你轻松理解 Nashorn JavaScript 引擎。更多关于 Nashorn 的信息请阅读 这里, 这里 和 这里. 如果你是要用 Nashorn 编写 Shell 脚本的话可以参考 这里.

过去我也发表了一些 文章 是关于如何在 Nashron 引擎中使用 Backbone.js 模型数据的。如果你想要了解更多 Java 8 的话可以去看看我的文章 Java 8 Tutorial 和 Java 8 Stream Tutorial.

以上がJava8用Nashornスクリプトエンジンの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。