Java8的Nashorn脚本引擎教程
本⽂为了解所有关于 Nashorn JavaScript 引擎易于理解的代码例⼦。 Nashorn JavaScript 引擎是Java SE 8的⼀部分,它与其它像Google V8 (它是Google Chrome 和Node.js的引擎)的独⽴引擎相互竞争。 Nashorn 扩展了Java在JVM上运⾏动态JavaScript脚本的能⼒。
在接下来的⼤约15分钟⾥,您将学习如何在 JVM 上动态运⾏ JavaScript。通过⼀些简短的代码⽰例演⽰最近 Nashorn 的语⾔特性。学习Java 与 JavaScript 的相互调⽤。最后包括如何在⽇常的 Java 业务中整合动态脚本。
使⽤Nashorn
Nashorn javascript 引擎要么在java程序中以编程的⽅式使⽤要么在命令⾏⼯具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代码,⾸先使⽤原先 (旧版Java中来⾃Mozilla的引擎)中的包javax.script来创建⼀个nashorn脚本引擎。.
既可以向上⾯那样把JavaScript代码作为⼀个字符串来直接执⾏,也可放⼊⼀个js脚本⽂件中,如:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval(new FileReader("script.js"));
Nashorn javascript是基于,但nashorn后续版本将⽀持 ECMAScript 6:
当前Nashorn的策略是遵循ECMAScript规范。当我们发布JDK 8时,我们将实现ECMAScript 5.1标准。
后续的 Nashorn的版本将实现标准。
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: " + String.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.Class());
// 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", w());
// [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 可通过 pe API 来引⽤ Java 类。这跟在 Java 类中引⼊其他类是类似的。当定义了 Java 类型后我们可直接调⽤其静态⽅法 fun1() 并打印结果到 sout。因为⽅法是静态的,所以我们⽆需创建类实例。
var MyJavaClass = pe('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.Class());
}
为了解引擎如何处理类型转换,我使⽤不同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,所以你不应该在客户端使⽤这些类:
ScriptObjectMirror
当使⽤ScriptObjectMirror把本地JavaScript对象传⼊时,实际上是有⼀个java对象表⽰JavaScript 对象。 ScriptObjectMirror 实现了接⼝
与jdk.nashorn.api内部的映射。这个包下的类⽬的就是⽤于客户端代码使⽤。
下⼀个⽰例更改参数类型Object为ScriptObjectMirror,因此我们能获取到传⼊JavaScript中对象的⼀些信息:
static void fun3(ScriptObjectMirror mirror) {
System.out.ClassName() + ": " +
}
当我们把传递对象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;
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 = pe("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) {
ssage); // 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的集合来代替数组。⾸先定义使⽤pe定义⼀个java类型,⽽后根据需要创建⼀个实例。
var ArrayList = pe('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 的类型可以简单的通过d进⾏扩展,在下个例⼦你将在脚本中创建⼀个多线程⽰例:
var Runnable = pe('java.lang.Runnable');
var Printer = d(Runnable, {
run: function() {
print('printed from a separate thread');
}
});
var Thread = pe('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 = pe('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 = pe('java.util.Date');
var date = new Date();
ar); // 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.AbsolutePath());
// /path/to/my/script.js
}
数组转换
有些包时可以直接使⽤⽽不必利⽤pe或JavaImporter引⼊,如 java.util:
js脚本编程入门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(String.call(jsArray)); // [object Array]
其他的⽅式:
var javaArray = ([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
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论