Tag Archives: nashorn

Java 栈上的 JavaScript

注:以上视频在 youtube 上,你懂得该怎么做。

这是我在 ShenJS 2015 上面所做的一个 lighting talk,当时的题目叫做 Enterprise JavaScript。

其实那天准备了很多演示和代码片段,无奈时间太短,只把 slides 简单过了一下就没有时间了,于是在这里做一个完整的介绍。

Enterprise JavaScript 是一个非常大的话题,会涉及到跟各种 Java 中间件的互操作问题,这里仅就如何在一个完整的 Java 栈上面以 JavaScript 作为首要编程语言来提高 Java 系统的开发效率展开讨论。

作为一门常年把持 TIOBE 排行榜冠军的静态编译类语言,Java 固然有数不胜数的优点,以至于其在企业级应用开发领域拥有无可替代的地位。但是,相信每一个 Java 开发人员都会在日常工作中忍受其种种弊端带来的烦恼,其中最令人发指的也正是其静态编译类的特性。静态类型在减少开发人员出错可能性的同时降低了语言的动态扩展能力;而编译执行除了能保证代码的编译期校验以外,更多的就只剩下无尽的等待。尽管现在大多数的工具链和集成开发环境都可以做到增量编译,但是增量编译后可执行代码的热部署能力却是差强人意。尽管有一些商业的开源的解决方案,但总归不是天生特性,使用很不方便。

其实针对 Java 缺陷的改良,社区始终也没有停止过尝试。著名的成果包括 ScalaGroovyJythonJRuby 以及我们今天要讨论的 JavaScript。社区通常都有这么一个共识:Java 虚拟机是个好东西,但 Java 语言本身不一定。因此改良的重点全部集中在语言层面,通过引入具有不同特性的新语言,经过编译器或解析器翻译成 Java 字节码,再交给虚拟机来运行。

另一方面,JavaScript 近两年人气爆棚,有逐渐发展成为全栈开发语言的趋势。尽管在 Node 平台上使用 JavaScript 开发后端应用早已是习以为常的事情,但是在 Java 平台上,这件事情就显得不那么容易了。说到在 Java 开发栈上运行 JavaScript 就不能不提大名鼎鼎的 Rhino。这是早在 1997 年就已经出现的 JavaScript 引擎,并完全由 Java 代码编写而成。后来被 JDK 1.6 收纳为官方默认的 JavaScript 引擎。这也就意味着在整个 Java 发展历史中,都是一直可以支持运行 JavaScript 的。不过由于大环境(在 Java 平台上面跑 JavaScript 毕竟接受度不高)以及 Java 虚拟机对动态语言支持能力欠缺等问题,使得这件事情并没有流行起来。

另外一个导致 Rhino 不够流行的原因,就是对其性能的诟病。在早期的时候 Rhino 是将 JavaScript 代码直接编译为 Java Class,运行期性能非常出色,往往可以击败很多用 C 实现的基于 JIT 技术的引擎。不过生成 Java 二进制代码和装载这些生成的类是个非常重量级的操作,需要耗费大量时间,而且编译过程产生的许多无用中间类和字符串无法被虚拟机垃圾回收掉。不过后来引入了解析模式以后这些问题就得到了解决。一些 性能 测试 报告 显示出 Rhino 的性能依旧非常良好。

后来 Java 官方也意识到虚拟机对动态语言欠缺支持以后,就从 JDK 1.7 开始引入了 invokedynamic 的技术,可以有效提升动态语言的运行性能,而且 Oracle 也在 JDK 1.8 中自带了一个基于此技术实现的新一代的 JavaScript 引擎 Nashorn,终于让性能不再是什么大问题了。

javascript-in-jvm-2

针对在不同版本的 JDK 上运行 JavaScript,可以有不同的解决方案(如上图)。如果是在老版本的 1.6 上面,只能使用 Rhino,以及在 Rhino 之上封装的开发框架 Ringo,这个组合里面没有对 invokedynamic 的支持。当然由于向后兼容性的存在,其实这个解决方案是可以适用于后续 1.7 和 1.8 版本的。如果是在 1.7 版本上面尝试使用 JavaScript,那么可以考虑 DynJS 引擎,这是另外一个独立的 JavaScript 引擎实现,支持了 invokedynamic 技术,与此配套的还有一个 Nodyn 项目,是对 Node 的一个移植。不过很可惜,这套开发栈没等流行起来,就被后来的技术给超越了,因此除非你必须要坚守在 1.7 上面,否则并不建议采用。最后,也是这个话题的终极解决方案,就自然是升级到 JDK 1.8 并使用官方的 Nashorn 引擎,与其对应的框架可以选用 Vert.x。Vert.x 是由 Eclipse 开发的,实现了良好的事件驱动和异步非阻塞机制,这些都是 Ringo 所没有的。再有 Vert.x 还是 reactive 的。不过 Oracle 官方有一个基于 Nashorn 引擎的 Node 实现,叫做 avatar.js,不过已经有一段时间没有更新过了。

下面展示一些使用 JavaScript 开发 Java 应用的示例。

注:在尝试运行这些样例代码之前,请确认已经正确安装了 JDK 1.8u51 或以上版本、Vert.x 3.x 和 Maven 3.x。

一、操作 ArrayList

arraylist

二、Filter and Reduce

filter-and-reduce

三、Web 服务

webserver

四、Spring、Stick 和 Hibernate(SSH)

这是一个比较复杂的例子,首先项目使用 Maven 来管理,并通过 jetty-maven-plugin 来运行。项目内部使用 Spring 作为整个集成的核心,用 Hibernate 来实现数据访问。Stick 是基于 Ringo 的一个组件,实现了类似 express 一样的功能。下面的这段代码,主要演示了如何通过 JavaScript 实现一个 router,并在 router 内部调用 Spring 和 Hibernate 的 API 实现数据查询,并返回 JSON 结果。此项目的完整源代码请在 Github 上获取(这里面还包含了会上的演示文稿,所有的样例都在 demo 目录下)。

运行该项目前,需要首先安装一个 MySQL 数据库,并运行在默认的 3306 端口,root 用户的口令为 mysecretpassword。创建一个名为 ringo-ssh 的数据库,字符集为 UTF-8。然后使用如下的 SQL 语句创建一个数据库表 T_TASK,并插入一些数据:

另外,该项目依赖了我们公司的两个开源项目,分别是 origincdeio-runtime。请从 Github 上 clone 下来以后,进入项目目录,运行 mvn clean install 来编译和安装。注意执行的顺序为先 origin 再 cdeio-runtime。

最后,可以通过运行 mvn jetty:run 命令来启动这个示例。

注:如果是第一次运行该示例,Maven 会下载很多依赖包,这个过程根据网速快慢会长短不一,请耐心等待。

以下是系统运行后的控制台截图和 curl 的返回结果。

ringo-ssh-maven

ringo-ssh-curl

总之,在 Java 平台上使用 JavaScript 作为首要开发语言是完全可行的,在上面这个示例中,除了 Hibernate 必须的领域实体等是使用 Java 来编写的之外,其他的大部分逻辑都是可以用 JavaScript 完成的,而且可以跟各种 Java 中间件完美集成。有关这方面的深入介绍远不止一篇博客所能言尽,有兴趣了解更多的可以参考我公司的 CDEIO 平台。