jdk nashorn_Nashorn-JDK 8中Java和JavaScript的组合功能

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。

Shell脚本

可以使用jjs命令从命令行调用Nashorn引擎。 您可以不带任何参数地调用它,这将使您进入交互模式,或者可以传递要执行JavaScript文件的名称,也可以使用它代替Shell脚本,如下所示:

#!/usr/bin/env jjs
var name = $ARG[0];
print(name ? "Hello, ${name}!" : "Hello, world!");

要将程序参数传递给jjs,请在它们前面加上“-”。 因此,例如,您可以调用:

./hello-script.js -- Joe

如果没有前缀“-”,则该参数将被解释为文件名。

在Java和Java之间传递数据

如上所述,您可以直接从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中使用Java类

如前所述,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<Integer> 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<Integer> 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 JavaScript方言

正如我在简介中提到的那样,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));

Avatar.js

我们已经看到,使用Nashorn,我们已经将高级JavaScript引擎嵌入到Java中。 我们还看到,从Nashorn中我们可以访问任何Java类。 Avatar.js更进一步,将“ Node编程模型,API和模块生态系统引入Java平台”。 要了解其含义以及令人兴奋的原因,我们首先必须了解Node是什么。 Node基本上会提取Chrome的V8 JavaScript引擎,使其从命令行运行而无需浏览器。 因此,它使JavaScript不仅可以在浏览器中执行,而且可以在服务器端执行。 要以任何有意义的方式在服务器上执行JavaScript,您至少需要访问文件系统和网络。 为此,Node嵌入了一个名为libuv的库,该库以异步方式执行此操作。 实际上,这意味着您对操作系统的调用永远不会阻塞,即使它们需要一段时间才能返回。 您可以提供一个回调函数,而不是阻塞函数,该函数将在调用完成后立即触发,并在有结果时传递结果。

有数家公司将Node用于严肃的应用程序,其中包括WalmartPaypal

让我们看一下我从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 )看到了这种库的几个用例。 我主要同意其中两个,即

  1. 您有一个Node应用程序,并且想使用某些Java库来补充Node API
  2. 您想切换到JavaScript和Node API,但需要部分或完全嵌入旧版Java代码

两种用例都可以通过使用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值