Joe Gage 盖奇·乔

“First time in my life I made a pretty penny.And, figured I'd come home and spend time with my mothr for Christmas.”

“有生以来第一次挣了很多钱,于是,我想回家陪陪我妈一起过圣诞节”

一、基础介绍

从JDK 6开始,Java就已经捆绑了JavaScript引擎,该引擎基于Mozilla的Rhino。该特性允许开发人员将JavaScript代码嵌入到Java中,甚至从嵌入的JavaScript中调用Java。此外,它还提供了使用jrunscript从命令行运行JavaScript的能力。如果不需要非常好的性能,并且可以接受ECMAScript 3有限的功能集的话,那它相当不错了。

从JDK 8开始,Nashorn取代Rhino成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的invokedynamic,将JavaScript编译成Java字节码。

与先前的Rhino实现相比,这带来了2到10倍的性能提升,虽然它仍然比Chrome和Node.js中的V8引擎要差一些。

  
我们先来个例子感觉一下java中使用JavaScript:
  1. ScriptEngineManager manager = new ScriptEngineManager();
  2. ScriptEngine engine = manager.getEngineByName( "JavaScript" );
  3.  
  4. System.out.println( engine.getClass().getName() );
  5. try {
  6. System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
  7. }catch (javax.script.ScriptException e){
  8. e.printStackTrace();
  9. }

输出如下:

  1. dk.nashorn.api.scripting.NashornScriptEngine
  2. Result: 2

与Java相比,使用JavaScript进行JavaFX开发会快很多。

二、在哪里使用JS

Shell脚本

Nashorn引擎可以使用jjs命令从命令行调用。你可以不带任何参数调用它,这会将你带入一个交互模式,或者你可以传递一个希望执行的JavaScript文件名,或者你可以用它作为shell脚本的替代,像这样:

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

向jjs传递程序参数,需要加“—”前缀。因此举例来说,你可以这样调用:

  1. ./hello-script.js Joe

如果没有“—”前缀,参数会被解释为文件名。

向Java传递数据或者从Java传出数据

正如上文所说的那样,你可以从Java代码直接调用JavaScript;只需获取一个引擎对象并调用它的“eval”方法。你可以将数据作为字符串显式传递……

  1. ScriptEngineManager scriptEngineManager =
  2. new ScriptEngineManager();
  3. ScriptEngine nashorn =
  4. scriptEngineManager.getEngineByName("nashorn");
  5. String name = "Olli";
  6. nashorn.eval("print('" + name + "')");

……或者你可以在Java中传递绑定,它们是可以从JavaScript引擎内部访问的全局变量:

  1. int valueIn = 10;
  2. SimpleBindings simpleBindings = new SimpleBindings();
  3. simpleBindings.put("globalValue", valueIn);
  4. nashorn.eval("print (globalValue)", simpleBindings);

JavaScript eval的求值结果将会从引擎的“eval”方法返回:

  1. Integer result = (Integer) nashorn.eval("1 + 2");
  2. assert(result == 3);

在Nashorn中使用Java类

前面已经提到,Nashorn最强大的功能之一源于在JavaScript中调用Java类。你不仅能够访问类并创建实例,你还可以继承他们,调用他们的静态方法,几乎可以做任何你能在Java中做的事。

作为一个例子,让我们看下来龙去脉。JavaScript没有任何语言特性是面向并发的,所有常见的运行时环境都是单线程的,或者至少没有任何共享状态。有趣的是,在Nashorn环境中,JavaScript确实可以并发运行,并且有共享状态,就像在Java中一样:

  1. // 访问Java类Thread
  2. var Thread = Java.type("java.lang.Thread");
  3.  
  4. // 带有run方法的子类
  5. var MyThread = Java.extend(Thread, {
  6. run: function() {
  7. print("Run in separate thread");
  8. }
  9. });
  10. var th = new MyThread();
  11. th.start();
  12. th.join();

请注意,从Nashorn访问类的规范做法是使用Java.type,并且可以使用Java.extend扩展一个类。

Nashorn JavaScript特有的方言

正如简介部分所提到的那样,Nashorn支持的JavaScript实现了ECMAScript 5.1版本及一些扩展。我并不建议使用这些扩展,因为它们既不是Java,也不是JavaScript,两类开发人员都会觉得它不正常。另一方面,有两个扩展在整个Oracle文档中被大量使用,因此,我们应该了解它们。首先,让我们为了解第一个扩展做些准备。正如前文所述,开发人员可以使用Java.extend从JavaScript中扩展一个Java类。如果需要继承一个抽象Java类或者实现一个接口,那么可以使用一种更简便的语法。在这种情况下,开发人员实际上可以调用抽象类或接口的构造函数,并传入一个描述方法实现的JavaScript对象常量。这种常量不过是name/value对,你可能了解JSON格式,这与那个类似。这使我们可以像下面这样实现Runnable接口:

  1. var r = new java.lang.Runnable({
  2. run: function() {
  3. print("running...\n");
  4. }
  5. });

在这个例子中,一个对象常量指定了run方法的实现,我们实际上是用它调用了Runnable的构造函数。注意,这是Nashorn的实现提供给我们的一种方式,否则,我们无法在JavaScript这样做。

示例代码已经与我们在Java中以匿名内部类实现接口的方式类似了,但还不完全一样。这将我们带到了第一个扩展,它允许开发人员在调用构造函数时在右括号“)”后面传递最后一个参数。这种做法的代码如下:

  1. var r = new java.lang.Runnable() {
  2. run: function() {
  3. print("running...\n");
  4. }
  5. };

……它实现了完全相同的功能,但更像Java。

第二个常用的扩展一种函数的简便写法,它允许删除单行函数方法体中的两个花括号以及return语句。这样,上一节中的例子:

  1. list.forEach(function(el) { print(el) } );

可以表达的更简洁一些:

  1. list.forEach(function(el) print(el));

Avatar.js

我们已经看到,有了Nashorn,我们就有了一个嵌入到Java的优秀的JavaScript引擎。我们也已经看到,我们可以从Nashorn访问任意Java类。Avatar.js更进一步,它“为Java平台带来了Node编程模型、API和模块生态系统”。要了解这意味着什么以及它为什么令人振奋,我们首先必须了解Node是什么。从根本上说,Node是将Chrome的V8 JavaScript引擎剥离出来,使它可以从命令行运行,而不再需要浏览器。这样,JavaScript就不是只能在浏览器中运行了,而且可以在服务器端运行。在服务器端以任何有意义的方式运行JavaScript都至少需要访问文件系统和网络。为了做到这一点,Node内嵌了一个名为libnv的库,以异步方式实现该项功能。实际上,这意味着操作系统调用永远不会阻塞,即使它过一段时间才能返回。开发人员需要提供一个回调函数代替阻塞。该函数会在调用完成时立即触发,如果有任何结果就返回。

有若干公司都在重要的应用程序中使用了Node,其中包括Walmart和Paypal。

让我们来看一个JavaScript的小例子,它是我根据Node网站上的例子改写而来:

  1. //加载“http”模块(这是阻塞的)来处理http请求
  2. var http = require('http');
  3.  
  4. //当有请求时,返回“Hello,World\n”
  5. function handleRequest(req, res) {
  6. res.writeHead(200, {'Content-Type': 'text/plain'});
  7. res.end('Hello, World\n');
  8. }
  9.  
  10. //监听localhost,端口1337
  11. //并提供回调函数handleRequest
  12. //这里体现了其非阻塞/异步特性
  13. http.createServer(handleRequest).listen(1337, '127.0.0.1');
  14.  
  15. //记录到控制台,确保我们在沿着正确的方向前进
  16. console.log('Get your hello at http://127.0.0.1:1337/');

要运行这段代码,需要安装Node,然后将上述JavaScript代码保存到一个文件中。最后,将该文件作为一个参数调用Node。

将libuv绑定到Java类,并使JavaScript可以访问它们,Avatar.js旨在以这种方式提供与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并从JavaScript代码中调用任何需要的Java类来实现。我们已经看到,Nashorn支持这种做法。

下面我将举一个第一个应用场景的例子。JavaScript目前只有一种表示数值的类型,名为“number”。这相当于Java的“double”精度,并且有同样的限制。JavaScript的number,像Java的double一样,并不能表示任意的范围和精度,比如在计量货币时。

在Java中,我们可以使用BigDecimal,它正是用于此类情况。但JavaScript没有内置与此等效的类型,因此,我们就可以直接从JavaScript代码中访问BigDecimal类,安全地处理货币值。

让我们看一个Web服务示例,它计算某个数量的百分之几是多少。首先,需要有一个函数执行实际的计算:

  1. var BigDecimal = Java.type('java.math.BigDecimal');
  2.  
  3. function calculatePercentage(amount, percentage) {
  4. var result = new BigDecimal(amount).multiply(
  5. new BigDecimal(percentage)).divide(
  6. new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
  7. return result.toPlainString();
  8. }

JavaScript没有类型声明,除此之外,上述代码与我针对该任务编写的Java代码非常像:

  1. public static String calculate(String amount, String percentage) {
  2. BigDecimal result = new BigDecimal(amount).multiply(
  3. new BigDecimal(percentage)).divide(
  4. new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN);
  5. return result.toPlainString();
  6. }

我们只需要替换上文Node示例中的handleRequest函数就可以完成代码。替换后的代码如下:

  1. //加载工具模块“url”来解析url
  2. var url = require('url');
  3.  
  4. function handleRequest(req, res) {
  5. // '/calculate' Web服务地址
  6. if (url.parse(req.url).pathname === '/calculate') {
  7. var query = url.parse(req.url, true).query;
  8. //数量和百分比作为查询参数传入
  9. var result = calculatePercentage(query.amount,
  10. query.percentage);
  11. res.writeHead(200, {'Content-Type': 'text/plain'});
  12. res.end(result + '\n');
  13. }
  14. }

我们又使用了Node核心模块来处理请求URL,从中解析出查询参数amount和percentage。

当启动服务器(如前所述)并使用浏览器发出下面这样一个请求时,

  1. http://localhost:1337/calculate?
  2. amount=99700000000000000086958613&percentage=7.59

就会得到正确的结果“7567230000000000006600158.73”。这在单纯使用JavaScript的“number”类型时是不可能。

当你决定将现有的JEE应用程序迁移到JavaScript和Node时,第二个应用场景就有意义了。在这种情况下,你很容易就可以从JavaScript代码内访问现有的所有服务。另一个相关的应用场景是,在使用JavaScript和Node构建新的服务器功能时,仍然可以受益于现有的JEE服务。

此外,基于Avatar.js的Avatar项目也朝着相同的方向发展。该项目的详细信息超出了本文的讨论范围,但读者可以阅读这份Oracle公告做一个粗略的了解。该项目的基本思想是,用JavaScript编写应用程序,并访问JEE服务。Avatar项目包含Avatar.js的一个二进制分发包,但它需要Glassfish用于安装和开发。

小结

Nashorn项目增强了JDK 6中原有的Rhino实现,极大地提升了运行时间较长的应用程序的性能,例如用在Web服务器中的时候。Nashorn将Java与JavaScript集成,甚至还考虑了JDK 8的新Lambda表达式。Avatar.js带来了真正的创新,它基于这些特性构建,并提供了企业级Java与JavaScript代码的集成,同时在很大程度上与JavaScript服务器端编程事实上的标准兼容。

完整实例以及用于Mac OS X的Avatar.js二进制文件可以从Github上下载。

参考链接:

http://www.infoq.com/cn/articles/nashorn

图片来源:八恶人(movie)

Java 8新特性之 Nashorn(八恶人-6)的更多相关文章

  1. Java 8新特性探究(八)精简的JRE详解

    http://www.importnew.com/14926.html     首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 Java小组 工具资源 - 导航条 - 首页 所有文章 资讯 ...

  2. [转帖]Java 8新特性探究(八)精简的JRE详解

    Java 8新特性探究(八)精简的JRE详解 https://my.oschina.net/benhaile/blog/211804 精简版的api   撸了今年阿里.网易和美团的面试,我有一个重要发 ...

  3. Java 8 新特性终极版

    声明:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,翻译过程中发现并发编程网已经有同学翻译过了:Java 8 特性 – 终极手册,我还是坚持自己 ...

  4. 【整理】Java 8新特性总结

    闲语: 相比于今年三月份才发布的Java 10 ,发布已久的Java 8 已经算是老版本了(传闻Java 11将于9月25日发布....).然而很多报道表明:Java 9 和JJava10不是 LTS ...

  5. Java 8 新特性-菜鸟教程 (0) -Java 8 新特性

    Java 8 新特性 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本. Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 ...

  6. Java-Runoob-高级课程:Java 8 新特性

    ylbtech-Java-Runoob-高级课程:Java 8 新特性 1.返回顶部 1. Java 8 新特性 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本. Or ...

  7. Java 8 新特性终极指南

    1.前言 毫无疑问,Java 8的发布是自从Java5以来Java世界中最重大的事件,它在编译器.工具类和Java虚拟机等方面为Java语言带来的很多新特性.在本文中我们將一起关注下这些新变化,使用实 ...

  8. IBM Developer:Java 9 新特性概述

    Author: 成富 Date: Dec 28, 2017 Category: IBM-Developer (20) Tags: Java (27) 原文地址:https://www.ibm.com/ ...

  9. 最通俗易懂的 Java 11 新特性讲解

    大多数开发者还是沉浸在 Java 8 中,而 Java 14 将要在 2020 年 3 月 17 日发布了,而我还在写着 Java 11 的新特性.Java 11 是 Java 8 之后的第一个 LT ...

随机推荐

  1. Android将日志信息自动发送到指定的邮箱中 邮件的内容以附件形式发送

    今日整合了网上一些大神的例子(具体看了那些大神的?这个真不好意思我忘记了.下次再整合一定给大家补上,这次也只有默默的给那几个大神说声抱歉了.)做了一个“记录android项目中的日志信息,并将日志信息 ...

  2. ansible 变量定义和引用

    cat /etc/ansible/hosts [nodes]10.2.1.232 key=23210.2.1.43 key=43 cat debug.yaml ---- name: test how ...

  3. POJ2533&&1836&&3176

    终于写完了POJ的DP专题,然而都是水题233 这次也把题目分了一下,先挑3道特别简单的讲一下 2533 题意:求最长上升子序列. 很简单,用一般的DP或者二分优化都可以过去 这里懒得写一般DP了,其 ...

  4. mfc 进程的诞生和死亡

     进程概念  进程的诞生  进程的死亡 一. 进程: .简单的说 双击一个EXE图标时,系统就会产生一个相应的进程,分配相应的资源,并执行相应的代码. .标准一些的说法: 进程是一个具有独立功能 ...

  5. 蓝牙inquiry流程之Advertising Report

    setting 界面开始搜索的时候,通常也会同时进行le scan,这一点在inquiry流程之命令下发中已经讲述.此篇文章主要是分析一下对于controller 搜索到的广播包的处理.这里以Andr ...

  6. springboot项目生成jar包(带静态资源)方法

    [Maven]在pom.xml文件中使用resources插件的小作用 不过war包比较实用,毕竟独立的tomcat比较好控制

  7. 微信小程序实现各种特效实例

    写在前面 最近在负责一个微信小程序的前端以及前后端接口的对接的项目,整体上所有页面的布局我都已经搭建完成,里面有一些常用的特效,总结一下,希望对大家和我都能有所帮助 实例1:滚动tab选项卡 先看一下 ...

  8. SpringBoot日记——删除表单-Delete篇

    增删改查,我们这篇文章来介绍一下如何进行删除表单的操作,也就是我们页面中的删除按钮的功能. 下边写的可能看起来有点乱,请仔细的一步一步完成. 删除功能第一步,按钮功能实现 1. html的改变 来看, ...

  9. 自制一个H5图片拖拽、裁剪插件(原生JS)

    前言 如今的H5运营活动中,有很多都是让用户拍照或者上传图片,然后对照片加滤镜.加贴纸.评颜值之类的.尤其是一些拍照软件公司的运营活动几乎全部都是这样的. 博主也做过不少,为了省事就封装了一个简单的图 ...

  10. Redux系列x:源码解析

    写在前面 redux的源码很简洁,除了applyMiddleware比较绕难以理解外,大部分还是 这里假设读者对redux有一定了解,就不科普redux的概念和API啥的啦,这部分建议直接看官方文档. ...