jdk nashorn
从JDK 6开始,Java附带了基于Mozilla的Rhino的捆绑JavaScript引擎 。 此功能使您可以将JavaScript代码嵌入Java,甚至可以从嵌入式JavaScript调用Java。 此外,它还提供了使用jrunscript从命令行运行JavaScript的功能。 如果您不需要出色的性能,并且可以使用有限的ECMAScript 3功能集,那将非常不错。
从JDK 8开始, Nashorn取代Rhino成为Java的嵌入式JavaScript引擎。 Nashorn支持完整的ECMAScript 5.1规范以及一些扩展 。 它JavaScript编译成Java字节码使用新的语言基础功能JSR 292 ,包括invokedynamic,在引入的JDK 7 。
尽管仍比Chrome和Node.js内的引擎V8稍差一点,但与以前的Rhino实施相比,它的性能提高了2到10倍。 如果您对实现的细节感兴趣,可以查看2013 JVM Language Summit的这些幻灯片。
由于Nashorn随JDK 8一起提供,它还为功能接口增加了非常简洁的支持,我们将在稍后详细介绍。
让我们从一个非常小的例子开始。 首先,您可能要安装JDK 8和NetBeans,IntelliJ IDEA或Eclipse。 所有这些都至少为集成JavaScript开发提供了基本支持。 让我们创建一个由以下两个示例文件组成的简单Java项目,并让程序运行:
(点击图片放大)
在第12行中,我们使用引擎的“ eval”方法评估任何JavaScript代码。 在这种情况下,我们只加载顶部JavaScript文件并对其进行评估。 您可能会发现“打印”不熟悉。 它不是JavaScript的内置函数,但是Nashorn提供了此功能以及其他在脚本环境中派上用场的便捷功能 。 您也可以将“ hello world”的打印直接嵌入到传递给“ eval”方法的字符串中,但是将JavaScript放在自己的文件中将为它打开整个工具世界。
Eclipse当前没有通过其JavaScript开发工具 (JSDT)项目提供专用的Nashorn支持,但是,支持JavaScript的基本工具和编辑:
(点击图片放大)
IntelliJ IDEA 13.1(社区版和终极版)提供了出色JavaScript和Nashorn支持。 有一个功能齐全的调试器,它甚至允许重构在Java和JavaScript之间进行同步。 因此,例如,如果您重命名从JavaScript引用的Java类或重命名从Java引用JavaScript文件,则IDE会跨语言修改相应的引用。
这是如何调试从Java调用JavaScript的示例(请注意,NetBeans还提供JavaScript调试,如下面的屏幕快照所示):
(点击图片放大)
您可能会说该工具看起来不错,新的实现可以修复性能以及合规性问题,但是为什么要使用它呢? 原因之一是通用脚本。 有时候,它可以派上用场,并且可以让它被解释。 有时,最好不要使用编译器,或者不担心静态类型。 也许您对Node.js编程模型感兴趣,该模型可与Java一起使用,正如我们在本文结尾处所看到的。 还有一种情况表明,使用JavaScript而不是Java可以更快地开发JavaFX。
可以使用jjs命令从命令行调用Nashorn引擎。 您可以不带任何参数地调用它,这将使您进入交互模式,或者可以传递要执行JavaScript文件的名称,也可以使用它代替Shell脚本,如下所示:
#!/usr/bin/env jjs
var name = $ARG[0];
print(name ? "Hello, ${name}!" : "Hello, world!");
要将程序参数传递给jjs,请在它们前面加上“-”。 因此,例如,您可以调用:
./hello-script.js -- Joe
如果没有前缀“-”,则该参数将被解释为文件名。
如上所述,您可以直接从Java代码调用JavaScript; 只需获取引擎并调用其“ eval”方法即可。 您可以将数据作为字符串显式传递...
ScriptEngineManager scriptEngineManager =
new ScriptEngineManager();
ScriptEngine nashorn =
scriptEngineManager.getEngineByName("nashorn");
String name = "Olli";
nashorn.eval("print('" + name + "')");
…或者您可以传递来自Java的绑定,这些绑定可以从JavaScript引擎内部作为全局变量进行访问:
int valueIn = 10;
SimpleBindings simpleBindings = new SimpleBindings();
simpleBindings.put("globalValue", valueIn);
nashorn.eval("print (globalValue)", simpleBindings);
JavaScript评估计算的结果将从引擎的“ eval”方法返回:
Integer result = (Integer) nashorn.eval("1 + 2");
assert(result == 3);
如前所述,Nashorn的最强大功能之一是从JavaScript内部调用Java类。 您不仅可以访问类并创建实例,还可以对它们进行子类化,调用它们的静态成员,以及几乎可以执行的所有Java工作。
作为示例,让我们看一下线程。 JavaScript没有用于并发的任何语言功能,并且所有常见的运行时都是单线程的,或者至少没有任何共享状态。 有趣的是,在Nashorn环境中,JavaScript实际上可以在共享状态下并发运行,就像在Java中一样:
// this is how we get access to Java class Thread
var Thread = Java.type("java.lang.Thread");
// subclass with our run method
var MyThread = Java.extend(Thread, {
run: function() {
print("Run in separate thread");
}
});
var th = new MyThread();
th.start();
th.join();
请注意,从Nashorn访问类的规范方法是使用Java.type ,您可以使用Java.extend扩展类。
从所有方面来看,随着JDK 8的发布,Java(至少在一定程度上)已成为一种功能 语言 。 现在,您可以在集合上使用高阶函数,例如,对它们的元素进行迭代。 高阶函数是将另一个函数作为参数并对其进行有意义的处理的函数。 看一下Java中的这个例子
List list = Arrays.asList(3, 4, 1, 2);
list.forEach(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
在此示例中,我们不再像传统上那样使用“外部”循环遍历元素,而是将“消费者”函数传递给“ forEach”操作,该函数执行更高阶的“内部循环”操作消费者的“接受”方法,即一步一步地传递集合中的每个元素。
如前所述,用于这种高阶函数的函数语言方法宁愿接受函数参数,也不接受对象。 尽管传递对函数本身的引用在传统上不是Java的专长,但JDK 8现在具有一些语法糖,可用于仅使用lambda表达式(也称为“闭包”)来表达它们。 例如:
List list = Arrays.asList(3, 4, 1, 2);
list.forEach(el -> System.out.println(el));
在这种情况下,“ forEach”的参数具有这种函数引用的形式。 这是可能的,因为Consumer是一个功能接口(有时称为Single Abstract Method类型或“ SAM”)。
那么,为什么我们在Nashorn讨论中谈论lambda? 因为在JavaScript中,您也可以编写这样的代码,因此在这种情况下,Nashorn尤其准备弥合Java和JavaScript之间的鸿沟。 特别是,它甚至允许您将普通JavaScript函数作为功能接口(SAM类型)的实现来传递。
让我们看一些普通JavaScript代码,这些代码与上面的Java代码具有相同的功能。 请注意,JavaScript中没有内置列表类型,只有数组。 但是这些数组是动态调整大小的,并且具有与Java列表可比的方法。 因此,在此示例中,我们将调用JavaScript数组的“ forEach”方法:
var jsArray = [4,1,3,2];
jsArray.forEach(function(el) { print(el) } );
相似之处显而易见。 但这还不是全部。 您还可以将这样JavaScript数组转换为Java列表:
var list = java.util.Arrays.asList(jsArray);
看到? 是的,这是Nashorn中运行JavaScript。 由于这现在是Java列表,因此可以调用其“ forEach”方法。 请注意,这与我们在JavaScript数组上调用的“ forEach”方法不同,而是在集合上定义的Java的“ forEach”方法。 尽管如此,我们还是在这里传递一个普通JavaScript函数:
list.forEach(function(el) { print(el) } );
Nashorn允许我们在需要功能接口(SAM类型)的地方提供简单JavaScript函数引用。 因此,这不仅可以通过Java实现,还可以通过JavaScript实现。
下一个版本的ECMAScript-预计将于今年最终发布-将包含一些简短的函数语法,使它们几乎可以像Java lambda一样编写,除了使用双箭头=>之外 。 这将进一步推动对齐。
正如我在简介中提到的那样,Nashorn支持ECMAScript 5.1版本中JavaScript 以及一些扩展 。 我不一定建议使用这些扩展名,因为它们既不是Java也不是JavaScript,它们对于任何一个开发人员都会感到不自然。 另一方面,在Oracle文档中使用了两个扩展,因此我们应该熟悉它们。
首先,让我们为第一次扩展奠定基础。 如前所述,您可以使用Java.extend从JavaScript扩展Java类。 如果要继承抽象Java类或实现接口,则可以使用更方便的语法。 在这种情况下,您可以虚拟地调用抽象类或接口的构造函数,并传入描述实现方法JavaScript对象文字。 JavaScript对象文字只是名称/值对,类似于您从JSON格式中可能了解的内容。 这使我们可以像下面这样实现Runnable接口:
var r = new java.lang.Runnable({
run: function() {
print("running...\n");
}
});
在此示例中,我们实际上使用指定了run方法的实现的对象常量来调用Runnable的构造函数。 请注意,这是Nashorn实现为我们提供的功能,否则将无法在JavaScript中实现。
该示例的代码已经看起来类似于我们将接口实现为Java中的匿名内部类的方式,但并不完全相同。 这将我们带到第一个扩展名,该扩展名使您可以在进行构造函数调用时在结束“)”之后传递最后一个参数。 这样做,我们的代码如下所示:
var r = new java.lang.Runnable() {
run: function() {
print("running...\n");
}
};
…完全一样,但是与Java更加相似。
第二个常用扩展名是函数的快捷方式,它使您可以在一行函数中省略花括号以及方法主体的return语句。 因此,上一节中的示例:
list.forEach(function(el) { print(el) } );
可以表示为更简洁:
list.forEach(function(el) print(el));
我们已经看到,使用Nashorn,我们已经将高级JavaScript引擎嵌入到Java中。 我们还看到,从Nashorn中我们可以访问任何Java类。 Avatar.js更进一步,将“ Node编程模型,API和模块生态系统引入Java平台”。 要了解其含义以及令人兴奋的原因,我们首先必须了解Node是什么。 Node基本上会提取Chrome的V8 JavaScript引擎,使其从命令行运行而无需浏览器。 因此,它使JavaScript不仅可以在浏览器中执行,而且可以在服务器端执行。 要以任何有意义的方式在服务器上执行JavaScript,您至少需要访问文件系统和网络。 为此,Node嵌入了一个名为libuv的库,该库以异步方式执行此操作。 实际上,这意味着您对操作系统的调用永远不会阻塞,即使它们需要一段时间才能返回。 您可以提供一个回调函数,而不是阻塞函数,该函数将在调用完成后立即触发,并在有结果时传递结果。
有数家公司将Node用于严肃的应用程序,其中包括Walmart和Paypal 。
让我们看一下我从Node网站改编的一个JavaScript小示例:
// load module 'http' (this is blocking) to handle http requests
var http = require('http');
// when there is a request we return 'Hello, World\n'
function handleRequest(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello, World\n');
}
// we listen on localhost, port 1337
// and give handleRequest as call back
// you see the non-blocking / asynchronous nature here
http.createServer(handleRequest).listen(1337, '127.0.0.1');
// logs to the console to reassure that we are on our way
console.log('Get your hello at http://127.0.0.1:1337/ ');
要运行此代码,您需要安装Node,然后将上面JavaScript代码保存到文件中,最后,以该文件为参数调用Node。
Avatar.js的目标是通过将libuv绑定到Java类,然后使它们可被JavaScript访问,从而提供与Node相同的核心API。 即使这听起来很麻烦,但效果却出奇的好。 Avatar.js支持大量的Node模块,对Node的主流Web框架“ express ”的支持表明它确实可以与许多现有项目一起使用。
不幸的是,在撰写本文时,Avatar.js没有二进制发行版。 有一个自述文件 ,说明了如何从源代码进行构建,但是如果您不太想从头开始构建,则也可以在不构建自己的情况下获取二进制文件 。 两种方法都行得通,但我建议第二种方法以获得更快的结果。
设置好二进制文件并将其放入lib文件夹后,您将使用以下方法调用Avatar.js框架:
java -Djava.library.path=lib -jar lib/avatar-js.jar helloWorld.js
我们假设演示服务器(上面的代码)保存在名为“ helloWorld.js”的文件中。
再次,让我们问,这为什么有用? Oracle的好人( 幻灯片10 )看到了这种库的几个用例。 我主要同意其中两个,即
两种用例都可以通过使用Avatar.js并从Nashorn支持JavaScript代码中调用任何必需的Java类来工作,如我们所见。
让我给你一个第一个用例的例子。 JavaScript当前只有一种表示数字的类型,称为“数字”。 这将等同于Java的“双精度”精度,但有相同的限制。 JavaScript的数字(例如Java的double)无法表达任意范围和精度,例如在处理金钱时。
在Java中,您可以使用BigDecimal,它完全支持这一点。 但是JavaScript没有内置等效项,因此您可以从JavaScript代码访问BigDecimal类,并可以安全地处理货币值。
让我们看一个示例Web服务,它计算一定数量的百分比。 首先,我们需要一个执行实际计算的函数:
var BigDecimal = Java.type('java.math.BigDecimal');
function calculatePercentage(amount, percentage) {
var result = new BigDecimal(amount).multiply(
new BigDecimal(percentage)).divide(
new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
return result.toPlainString();
}
在JavaScript中,没有声明的类型,但除此之外,该代码看起来与我为此任务编写的Java代码非常相似:
public static String calculate(String amount, String percentage) {
BigDecimal result = new BigDecimal(amount).multiply(
new BigDecimal(percentage)).divide(
new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
return result.toPlainString();
}
我们只需要替换上面的Node示例的handleRequest函数即可完成我们的代码。 像这样
// load utility module 'url' to parse url
var url = require('url');
function handleRequest(req, res) {
// '/calculate' is the path of our web service
if (url.parse(req.url).pathname === '/calculate') {
var query = url.parse(req.url, true).query;
// amount and percentage are passed in as query parameters
var result = calculatePercentage(query.amount,
query.percentage);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(result + '\n');
}
}
我们使用Node的第二个核心模块来处理请求的URL,以解析出数量和百分比的查询参数。
当我启动服务器(如上所示)并发出这样的请求时
http://localhost:1337/calculate?amount=99700000000000000086958613&percentage=7.59
使用网络浏览器,我得到正确的答案“ 7567230000000000006600158.73”,而使用JavaScript的普通“数字”类型是不可能的。
当您决定将现有的JEE应用程序迁移到JavaScript和Node时,第二个用例将很有意义。 在这种情况下,您可以轻松地从JavaScript中访问所有现有服务。 另一个相关的用例是使用JavaScript和Node构建新的服务器功能,并且仍然可以从现有的JEE服务中受益。
同样的方向,还有基于Avatar.js的Project Avatar 。 详细信息不在本文讨论范围之内,但是要获得快速概述,请查看此Oracle公告 。 基本思想是用JavaScript编写应用程序并访问JEE服务。 Project Avatar带有用于Avatar.js的组合二进制发行版,但需要Glassfish进行安装和开发。
Nashorn项目通过大大提高长时间运行的应用程序(例如在Web服务器内部使用)的性能,增强了原始JDK 6 Rhino的实现。 Nashorn将Java与JavaScript集成在一起,甚至考虑了JDK 8的新lambda。 真正的创新来自Avatar.js,它建立在这些功能的基础上,并提供企业Java和JavaScript代码的集成,同时与JavaScript服务器编程的事实上的标准在很大程度上兼容。
可以在Github上找到完整的示例,包括Mac OS X的Avatar.js二进制文件。
翻译自: https://www.infoq.com/articles/nashorn/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
jdk nashorn