Java 8 的 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代码,首先使用原先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
,所以你不应该在客户端使用这些类:
Anything marked internal will likely change out from underneath you.
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]); // array[0] = "wrong type";
print(array[0]); // array[0] = "17.3";
print(array[0]); //
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); //
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); //
函数语法
如果只是简单的一行函数我们可以不用大括号:
function sqr(x) x * x;
print(sqr(3)); //
属性绑定
来自不同对象的属性可以绑定在一起:
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 开发者而言,编写命令行脚本从来没有如此简单过。
Java 8 的 Nashorn 脚本引擎教程的更多相关文章
- Rhino脚本引擎技术介绍
引用:http://p.primeton.com/articles/54c1e255be20aa4735000001 http://blog.csdn.net/u013292493/article/d ...
- Java 脚本引擎
脚本引擎,就是一个编程语言的解释器,它的功能是解释执行用户的程序文本,将它译成计算机能执行的机器代码,完成一系列的功能. Java脚本引擎简单地说就是指Java提供的一组兼容操作各种脚本语言的的API ...
- java 11 移除的一些其他内容,更简化的编译运行程序,Unicode 10,移除了不太使用的JavaEE模块和CORBA技术,废除Nashorn javascript引擎,不建议使用Pack200 相关api
移除的一些其他内容 移除项 移除了com.sun.awt.AWTUtilities 移除了sun.misc.Unsafe.defineClass, 使用java.lang.invoke.MethodH ...
- JDK8在Java转让Javascript脚本引擎动态地定义和运行代码
import java.lang.*; import java.util.Arrays; import java.util.List; import javax.script.Invocable; i ...
- Java中通过脚本引擎调用js函数
import java.io.*; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.scri ...
- [19/04/19-星期五] Java的动态性_脚本(Script,脚本)引擎执行JavaScript代码
一.概念 Java脚本引擎是jdk 6.0之后的新功能. 使得Java应用程序可以通过一套固定的接口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的. Java脚本API是连接Jav ...
- 8.4(Java学习笔记)java脚本引擎(Rhino)
一.java脚本引擎 java脚本引擎是沟通java和脚本语句之间的桥梁,可以通过对应的脚本引擎在java中调用各种脚本语言. 二.脚本引擎执行脚本代码 ScriptEngineManager:为Sc ...
- Java_脚本引擎_02_在Idea中进行Nashorn的Debug
一.前言 本文承接上一节:Java_脚本引擎_01_用法入门 这一节我们来看下怎么在idea中进行Nashorn的Debug ,又或者说怎么在Idea中进行js的Debug 注:idea本身就支持js ...
- java 脚本引擎执行js
为用到时,使用方便直接保存一下代码 package com.xzlf.reflectTest; import java.io.BufferedReader; import java.io.FileIn ...
随机推荐
- 理解CSS边框border
前面的话 边框是CSS盒模型属性中默默无闻的一个普通属性,CSS3的到来,但得边框属性重新焕发了光彩.本文将详细介绍CSS边框 基础样式 边框是一条以空格分隔的集合样式,包括边框粗细(边框宽度 ...
- 02.SQLServer性能优化之---牛逼的OSQL----大数据导入
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 上一篇:01.SQLServer性能优化之----强大的文件组----分盘存储 http ...
- 谈谈一些有趣的CSS题目(七)-- 消失的边界线问题
开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...
- TypeScript为Zepto编写LazyLoad插件
平时项目中使用的全部是jQuery框架,但是对于做webapp来说jQuery太过于庞大,当然你可以选择jQuery 2.*针对移动端的版本. 这里我采用移动端使用率比较多的zepto框架,他跟jqu ...
- 学习C的笔记
[unsigned] 16位系统中一个int能存储的数据的范围为-32768~32767,而unsigned能存储的数据范围则是0~65535.由于在计算机中,整数是以补码形式存放的.根据最高位的不同 ...
- ILJMALL project过程中遇到Fragment嵌套问题:IllegalArgumentException: Binary XML file line #23: Duplicate id
出现场景:当点击"分类"再返回"首页"时,发生error退出 BUG描述:Caused by: java.lang.IllegalArgumentExcep ...
- Markdown学习笔记
分为两步: 1.阅读Markdown中文官网的文档 2.下载MarkdownPad2将中文官网中文档的例子敲一遍,其中Markdownpad2为官网中推荐的编辑器 备注: 如果只看中文官网文档,不边看 ...
- js动态加载css和js
之前写了一个工具类点此链接里面含有这段代码,感觉用处挺多,特意提出来 var loadUtil = { /* * 方法说明:[动态加载js文件css文件] * 使用方法:loadUtil.loadjs ...
- 山寨Unity3D?搜狐畅游的免费开源游戏引擎Genesis-3D
在CSDN上看到了<搜狐畅游发布3D游戏引擎Genesis-3D 基于MIT协议开源>(http://www.csdn.net/article/2013-11-21/2817585-cha ...
- jexus5.8.2 linux x64专业版 配置https
一.环境 1.jexus版本:Jexus/5.8.2.8 Linux专业版 内置mono版本:Mono/4.6.2.7 2.操作系统:centOs7 jexus独立版由于是免安装版并且内置mono,所 ...