>Java >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 자바스크립트 엔진은 프로그래밍 방식으로 Java 프로그램에서 사용하거나 $JAVA_HOME/bin디렉토리에 있는 명령줄 도구 jjs에서 사용됩니다. 🎜>. 다음과 같이 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 스크립트 엔진을 생성합니다. .

은 위와 같이 문자열으로 직접 실행될 수도 있고 다음과 같은 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은 스크립트 파일에 정의된 JavaScript 함수를 직접 호출할 수 있는 Java 코드를 지원합니다. Java 개체를 매개변수로 함수에 전달하고 함수를 호출하는 Java 메서드에서 반환된 데이터를 받을 수 있습니다.

Java 측에서 다음 JavaScript 코드가 호출됩니다.

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 Invocable。NashornScriptEngine을 구현하는 으로 변환해야 합니다. 인터페이스 및 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

위 코드를 실행하면 콘솔에 세 줄의 정보가 인쇄됩니다. 인쇄 기능을 호출하면 출력이 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]

JavaScript 측에서 유형 정보를 잃지 않고 Java 객체를 전달할 수 있습니다. 스크립트 자체가 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

Nashorn은 java 메소드를 호출할 때 기본 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 object 내부 어댑터 클래스 대신. 이러한 클래스는 jdk.nashorn.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: 'bar',
    bar: 'foo'
});

// 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('java.util.ArrayList');
var list = new ArrayList();
list.add('a');
list.add('b');
list.add('c');

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

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

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

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

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('java.lang.Runnable');
var Printer = Java.extend(Runnable, {
    run: function() {
        print('printed from a separate thread');
    }
});

var Thread = Java.type('java.lang.Thread');
new Thread(new Printer()).start();

new Thread(function() {
    print('printed from another thread');
}).start();

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

参数重载

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

var System = Java.type('java.lang.System');
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('java.util.Date');
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: 'bar'};

Object.bindProperties(o1, o2);

print(o1.foo);    // bar
o1.foo = 'BAM';
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('com.winterbe.java8.SuperRunner');
var Runner = Java.extend(SuperRunner);

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

// super run
// on my run

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

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

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

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

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

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

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

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

loadWithNewGlobal('script.js');

命令行脚本

如果你对用 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.