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的符号链接,如下:


 
 
  1. $ cd /usr/bin 
  2. $ ln -s $JAVA_HOME/bin/jjs jjs 
  3. $ jjs 
  4. 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端调用:


 
 
  1. var fun1 = function(name) { 
  2.     print('Hi there from Javascript, ' + name); 
  3.     return "greetings from javascript"; }; var fun2 = function (object) { print("JS Class Definition: " + Object.prototype.toString.call(object)); 
  4. }; 

为了调用函数,你首先得把脚本引擎转换为 Invocable。NashornScriptEngine 实现了 Invocable 接口且定义一个调用JavaScript函数的方法 invokeFunction ,传入函数名即可。


 
 
  1. ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); 
  2. engine.eval(new FileReader("script.js")); 
  3.  
  4. Invocable invocable = (Invocable) engine; 
  5.  
  6. Object result = invocable.invokeFunction("fun1", "Peter Parker"); 
  7. System.out.println(result); 
  8. System.out.println(result.getClass()); 
  9.  
  10. // Hi there from Javascript, Peter Parker 
  11. // greetings from javascript 
  12. // class java.lang.String 

上述代码的执行将在控制台打印三行信息。调用 print 函数将输出内容通过管道送到 System.out 控制台,因此我们首先看到的是 JavaScript打印的信息。

现在我们通过传递任意的 Java 对象去调用第二个函数:


 
 
  1. invocable.invokeFunction("fun2", new Date()); 
  2. // [object java.util.Date] 
  3.  
  4. invocable.invokeFunction("fun2", LocalDateTime.now()); 
  5. // [object java.time.LocalDateTime] 
  6.  
  7. invocable.invokeFunction("fun2", new Person()); 
  8. // [object com.winterbe.java8.Person] 

你可以传递任意 Java 对象而不会在 JavaScript 这边丢失类型信息。因为脚本本身是在 JVM 虚拟机中执行的,我们可以完全利用 nashorn 引擎的 Java API 和外部库的强大功能。

在 JavaScript 端调用 Java 方法

在 JavaScript 中调用 Java 方法很简单。首先我们定义一个静态的 Java 方法:


 
 
  1. static String fun1(String name) { 
  2.     System.out.format("Hi there from Java, %s", name); 
  3.     return "greetings from java"; 
  4. } 

JavaScript 可通过 Java.type API 来引用 Java 类。这跟在 Java 类中引入其他类是类似的。当定义了 Java 类型后我们可直接调用其静态方法 fun1() 并打印结果到 sout。因为方法是静态的,所以我们无需创建类实例。


 
 
  1. var MyJavaClass = Java.type('my.package.MyJavaClass'); 
  2. var result = MyJavaClass.fun1('John Doe'); 
  3. print(result); 
  4. // Hi there from Java, John Doe 
  5. // greetings from java 

当调用java 方法时,Nashorn怎样处理原生JavaScript类型与java类型转换?让我们用一个简单的例子来发现。

下面的java方法简单打印实际的类方法参数的类型:


 
 
  1. static void fun2(Object object) { 
  2.     System.out.println(object.getClass()); 
  3. } 

为了解引擎如何处理类型转换,我使用不同JavaScript类型来调用java方法:


 
 
  1. MyJavaClass.fun2(123); 
  2. // class java.lang.Integer 
  3.  
  4. MyJavaClass.fun2(49.99); 
  5. // class java.lang.Double 
  6.  
  7. MyJavaClass.fun2(true); 
  8. // class java.lang.Boolean 
  9.  
  10. MyJavaClass.fun2("hi there") 
  11. // class java.lang.String 
  12.  
  13. MyJavaClass.fun2(new Number(23)); 
  14. // class jdk.nashorn.internal.objects.NativeNumber 
  15.  
  16. MyJavaClass.fun2(new Date()); 
  17. // class jdk.nashorn.internal.objects.NativeDate 
  18.  
  19. MyJavaClass.fun2(new RegExp()); 
  20. // class jdk.nashorn.internal.objects.NativeRegExp 
  21.  
  22. MyJavaClass.fun2({foo: 'bar'}); 
  23. // 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中对象的一些信息:


 
 
  1. static void fun3(ScriptObjectMirror mirror) { 
  2.     System.out.println(mirror.getClassName() + ": " + 
  3.         Arrays.toString(mirror.getOwnKeys(true))); 
  4. } 

当我们把传递对象hash到方法中,在Java端就能访问这些属性:


 
 
  1. MyJavaClass.fun3({ 
  2.     foo: 'bar', 
  3.     bar: 'foo' 
  4. }); 
  5.  
  6. // Object: [foo, bar] 

我们也可以在Java端调用JavaScript对象中的函数。我们首先定义一个JavaScript类型 Person,包含属性 firstName 、lastName 和函数getFullName。


 
 
  1. function Person(firstName, lastName) { 
  2.     this.firstName = firstName; 
  3.     this.lastName = lastName; 
  4.     this.getFullName = function() { 
  5.         return this.firstName + " " + this.lastName; 
  6.     } 
  7. } 

javascript 函数getFullName 能被 ScriptObjectMirror 的callMember()调用。


 
 
  1. static void fun4(ScriptObjectMirror person) { 
  2.     System.out.println("Full Name is: " + person.callMember("getFullName")); 
  3. } 

当我们传入一个新的person给java 方法时,我们能在控制台看到预期结果:


 
 
  1. var person1 = new Person("Peter", "Parker"); 
  2. MyJavaClass.fun4(person1); 
  3.  
  4. // Full Name is: Peter Parker 

语言扩展

Nashorn 定义一系列的语言和扩展了 ECMAScript 标准的API。 让我们直接进入最新的功能:

类型数组

原始javascript 数组时无类型的。 Nashorn 运行你在JavaScript中使用java数组:


 
 
  1. var IntArray = Java.type("int[]"); 
  2.  
  3. var array = new IntArray(5); 
  4. array[0] = 5; 
  5. array[1] = 4; 
  6. array[2] = 3; 
  7. array[3] = 2; 
  8. array[4] = 1; 
  9.  
  10. try { 
  11.     array[5] = 23; 
  12. } catch (e) { 
  13.     print(e.message);  // Array index out of range: 5 
  14. } 
  15.  
  16. array[0] = "17"; 
  17. print(array[0]);  // 17 
  18.  
  19. array[0] = "wrong type"; 
  20. print(array[0]);  // 0 
  21.  
  22. array[0] = "17.3"; 
  23. print(array[0]);  // 17 

int[] 数组的行为像一个真正的 java int 数组。 但当我们试图添加非整数的值的数组时,Nashorn 会执行隐式类型转换。 字符串会自动转换为int,这相当方便。

集合与For Each

我们可以使用java的集合来代替数组。首先定义使用 Java.type定义一个java类型,而后根据需要创建一个实例。


 
 
  1. var ArrayList = Java.type('java.util.ArrayList'); 
  2. var list = new ArrayList(); 
  3. list.add('a'); 
  4. list.add('b'); 
  5. list.add('c'); 
  6.  
  7. for each (var el in list) print(el);  // a, b, c

为了遍历集合和数组中的元素,Nashorn 引入了 for each 语句。这就像是 Java 的 for 循环一样。

这里是一个对集合元素进行遍历的例子,使用的是 :


 
 
  1. var map = new java.util.HashMap(); 
  2. map.put('foo', 'val1'); 
  3. map.put('bar', 'val2'); 
  4.  
  5. for each (var e in map.keySet()) print(e);  // foo, bar 
  6. for each (var e in map.values()) print(e);  // val1, val2 

Lambda 表达式和 Streams

似乎大家都比较喜欢 Lambda 和 Streams —— Nashorn 也是!虽然 ECMAScript 5.1 中缺少 Java 8 Lambda 表达式中的紧缩箭头的语法,但我们可以在接受 Lambda 表达式的地方使用函数来替代。


 
 
  1. var list2 = new java.util.ArrayList(); 
  2. list2.add("ddd2"); 
  3. list2.add("aaa2"); 
  4. list2.add("bbb1"); 
  5. list2.add("aaa1"); 
  6. list2.add("bbb3"); 
  7. list2.add("ccc"); 
  8. list2.add("bbb2"); 
  9. list2.add("ddd1"); 
  10.  
  11. list2 
  12.     .stream() 
  13.     .filter(function(el) { 
  14.         return el.startsWith("aaa"); 
  15.     }) 
  16.     .sorted() 
  17.     .forEach(function(el) { 
  18.         print(el); 
  19.     }); 
  20.     // aaa1, aaa2 

扩展类

Java 的类型可以简单的通过 Java.extend 进行扩展,在下个例子你将在脚本中创建一个多线程示例:


 
 
  1. var Runnable = Java.type('java.lang.Runnable'); 
  2. var Printer = Java.extend(Runnable, { 
  3.     run: function() { 
  4.         print('printed from a separate thread'); 
  5.     } 
  6. }); 
  7.  
  8. var Thread = Java.type('java.lang.Thread'); 
  9. new Thread(new Printer()).start(); 
  10.  
  11. new Thread(function() { 
  12.     print('printed from another thread'); 
  13. }).start(); 
  14.  
  15. // printed from a separate thread 
  16. // printed from another thread 

参数重载

方法和函数可以使用点符号或方括号来进行调用。


 
 
  1. var System = Java.type('java.lang.System'); 
  2. System.out.println(10);              // 10 
  3. System.out["println"](11.0);         // 11.0 
  4. System.out["println(double)"](12);   // 12.0 

在使用重载的参数来调用方法时可以传递可选参数来确定具体调用了哪个方法,如 println(double)。

Java Beans

我们不需要常规的用 getter 或者 setter 来访问类成员属性,可直接用属性名简单访问 Java Bean 中的属性。例如:


 
 
  1. var Date = Java.type('java.util.Date'); 
  2. var date = new Date(); 
  3. date.year += 1900; 
  4. print(date.year);  // 2014 

函数语法

如果只是简单的一行函数我们可以不用大括号:


 
 
  1. function sqr(x) x * x; 
  2. print(sqr(3));    // 9 

属性绑定

来自不同对象的属性可以绑定在一起:


 
 
  1. function sqr(x) x * x; 
  2. print(sqr(3));    // 9 

字符串处理

我喜欢字符串裁剪.


 
 
  1. print(" hehe".trimLeft()); // hehe print("hehe ".trimRight() + "he"); // hehehe 

在哪里

以防忘记你在哪里:


 
 
  1. print(__FILE__, __LINE__, __DIR__); 

Import 的范围

有时,这在一次性导入多个java 包时非常有用。我们可以使用JavaImporter并结合with,在with块范围内引用:


 
 
  1. var imports = new JavaImporter(java.io, java.lang); 
  2. with (imports) { 
  3.     var file = new File(__FILE__); 
  4.     System.out.println(file.getAbsolutePath()); 
  5.     // /path/to/my/script.js 
  6. } 

数组转换

有些包时可以直接使用而不必利用 Java.type 或JavaImporter引入,如 java.util:


 
 
  1. var list = new java.util.ArrayList(); 
  2. list.add("s1"); list.add("s2"); list.add("s3"); 

如下的代码演示了将java list转换为JavaScript的数组:


 
 
  1. var jsArray = Java.from(list); 
  2. print(jsArray);                                  // s1,s2,s3 
  3. print(Object.prototype.toString.call(jsArray));  // [object Array] 

其他的方式:


 
 
  1. var javaArray = Java.to([3, 5, 7, 11], "int[]"); 

调用父类函数

在 JavaScript 中访问重载的成员会有一点点尴尬,因为 ECMAScript 没有类似 Java 的 super 关键字一样的东西。所幸的是 Nashorn 有办法解决。

首先我们在 Java 代码中定义一个超类:


 
 
  1. class SuperRunner implements Runnable { 
  2.     @Override 
  3.     public void run() { 
  4.         System.out.println("super run"); 
  5.     } 
  6. } 

接下来我们在 JavaScript 中重载 SuperRunner 。创建一个新的 Runner 实例时请注意 Nashorn 的扩展语法:其重载成员的语法是参考 Java 的匿名对象的做法。


 
 
  1. var SuperRunner = Java.type('com.winterbe.java8.SuperRunner'); 
  2. var Runner = Java.extend(SuperRunner); 
  3.  
  4. var runner = new Runner() { 
  5.     run: function() { 
  6.         Java.super(runner).run(); 
  7.         print('on my run'); 
  8.     } 
  9. } 
  10. runner.run(); 
  11.  
  12. // super run 
  13. // on my run 

我们使用Java.super调用了重载方法 SuperRunner.run()。

在JavaScript中执行其它脚本是十分容易的。我们可以load函数载入本地或远程的脚本。

在我的很多web前端中都使用了 Underscore.js ,因此在Nashorn中我们可以重用 Underscore:


 
 
  1. load('http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js'); 
  2.  
  3. var odds = _.filter([1, 2, 3, 4, 5, 6], function (num) { 
  4.     return num % 2 == 1; 
  5. }); 
  6. print(odds);  // 1, 3, 5

扩展脚本的执行是在同一个 JavaScript 上下文中,因此我们可以直接访问 underscore 变量。记住脚本的加载可能会因为变量名的重叠导致代码出问题。

我们可以通过将加载的脚本文件放置到一个新的全局上下文来解决这个问题:


 
 
  1. loadWithNewGlobal('script.js'); 

命令行脚本

如果你对用 Java 编写命令行脚本很感兴趣的话,可以试试 Nake 。Nake 是一个为 Java 8 Nashorn 准备的简单 Make 工具。你可以在 Nakefile 文件中定义任务,然后使用 nake — myTask 来运行任务。任务使用 JavaScript 编写并通过 Nashorn 脚本模式运行,因此你可以让你的终端应用完全利用 Java 8 API 和其他 Java 库强大的功能。

对 Java 开发者而言,编写命令行脚本从来没有如此简单过。

总结

我希望这篇文章对你有用,可以让你轻松理解 Nashorn JavaScript 引擎。更多关于 Nashorn 的信息请阅读 这里, 这里 和 这里. 如果你是要用 Nashorn 编写 Shell 脚本的话可以参考 这里.

过去我也发表了一些 文章 是关于如何在 Nashron 引擎中使用 Backbone.js 模型数据的。如果你想要了解更多 Java 8 的话可以去看看我的文章 Java 8 Tutorial 和 Java 8 Stream Tutorial.

本文中的示例代码可以通过 GitHub 获取,你可以 fork 这个仓库并通过 Twitter 来给我反馈。

坚持编程!


作者:luke, coyee, CY2

来源:51CTO

相关文章
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
752 0
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
547 10
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
1701 4
|
存储 监控 Java
“代码跑着跑着,就变快了?”——揭秘Java性能幕后引擎:即时编译器
HotSpot虚拟机内置C1和C2两个即时编译器。C1启动快,适合快速执行;C2优化强,适合长期运行。自Java 9起,默认启用C2或分层编译。分层编译结合C1与C2优势,共分5层,逐步提升编译质量。方法调用计数器与循环回边计数器用于识别热点代码,触发JIT编译。循环回边计数器还可启动栈上替换(OSR),提升大循环性能。本文详解JIT编译机制与性能优化策略。
586 75
|
缓存 安全 Java
Java 并发新特性实战教程之核心特性详解与项目实战
本教程深入解析Java 8至Java 19并发编程新特性,涵盖CompletableFuture异步编程、StampedLock读写锁、Flow API响应式流、VarHandle内存访问及结构化并发等核心技术。结合电商订单处理、缓存系统、实时数据流、高性能计数器与用户资料聚合等实战案例,帮助开发者高效构建高并发、低延迟、易维护的Java应用。适合中高级Java开发者提升并发编程能力。
708 0
|
文字识别 监控 Java
顺丰同城抢单辅助脚本,顺丰骑士抢单辅助免封号,自动抢单神器【java版开源】
这是一套OCR实时检测订单列表并自动右滑的完整代码方案,适用于学习研究。代码包含四个主要模块:OCR处理(文字识别)、价格分析
|
Oracle Java 关系型数据库
java 编程基础入门级超级完整版教程详解
这份文档是针对Java编程入门学习者的超级完整版教程,涵盖了从环境搭建到实际项目应用的全方位内容。首先介绍了Java的基本概念与开发环境配置方法,随后深入讲解了基础语法、控制流程、面向对象编程的核心思想,并配以具体代码示例。接着探讨了常用类库与API的应用,如字符串操作、集合框架及文件处理等。最后通过一个学生成绩管理系统的实例,帮助读者将理论知识应用于实践。此外,还提供了进阶学习建议,引导学员逐步掌握更复杂的Java技术。适合初学者系统性学习Java编程。资源地址:[点击访问](https://pan.quark.cn/s/14fcf913bae6)。
1396 2
|
Java 关系型数据库 MySQL
Java汽车租赁系统源码(含数据库脚本)
Java汽车租赁系统源码(含数据库脚本)
812 4
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
988 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
消息中间件 Java 数据库
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」