天天看点

深入浅出Rhino:Java与JS互操作

  java se 8 新增主要功能

  2、原始java模块系统(“项目jigsaw”)将简化应用程序的构建、包装以及部署,让一个完全模块化的java平台能在服务器、客户和嵌入式系统上进行定制化部署。

  3、在jvm上的javascript改进,包括一个为jvm优化的全新javascript引擎nashorn和全面的java / javascript互操作性。

  4、具有javafx 3.0形式的下一代java客户端。包括多点触摸功能的现代设备支持。

  5、完成的hotspot / jrockit jvm集聚项目,包括性能增强和第二代的java flight recorder。

  其中,笔者最关心的是第三条,即jvm对javascript的改进。它的核心组件是javascript引擎nashorn,它实现了java与javascript互操作性。nashorn一词与rhino类似,汉语意思均为犀牛。而巧合的是,rhino就是javascript引擎,它的目的就是实现java与javascript的互操作性。那么rhino究竟是什么呢?为什么说nashorn是新一代javascript引擎?rhino有什么特性?rhino与java及javascript有什么关系呢?本文将会为您一一解答。

  什么是rhino?

  rhino 是javascript 的一种基于java的实现,原先由mozilla开发,现在被集成进入jdk 6.0。下面这两行代码恰好说明了这一点。

import sun.org.mozilla.javascript.internal.context; 

import sun.org.mozilla.javascript.internal.scriptable;

  rhino汉语意思为犀牛,它的名字来源于 o'reilly 关于 javascript 的书的封面,如图一所示。

深入浅出Rhino:Java与JS互操作

图一 “犀牛“的来源

  rhino的特点如下:

  javascript 1.5的全部特性

  ◆ 允许使用脚本直接操作java

  ◆ 提供javascript shell执行其它javascript脚本

  ◆ 提供javascript编译器将javascript源程序转换成java类文件

  rhino相关背景

  rhino的历史可追溯到1997 年。netscape计划开发java版的navigator,即javagator。它也就是 rhino 的前身。虽然 javagator 未能开花结果,但是rhino,作为netscape 对 javascript 的移植语言,经过时间考验存活了下来。

  如今,随着 rhino 开放源代码,越来越多的开发者参与了 rhino 的开发。随着rhino的愈加成熟,越来越多的用户选择使用了rhino。

rhino语言特点

  java是一种面对对象的编译型语言。它首先将源代码编译成二进制字节码(bytecode),然后依赖各种不同平台上的虚拟机来解释执行字节码,从而实现了“一次编译、到处执行”的跨平台特性。

  javascript是一种动态、弱类型、基于原型的客户端脚本语言。javascript 包括一个基于对象的 api,称为文档对象模型(document object model)或 dom,用以访问和操作 web 页面的内容,给html网页添加动态功能。

  rhino是一个介于java与javascript之间的语言。它的基础是 java 语言,这使得它简单易学,但相比于javascript脚本语言来说,它又太过复杂。不过,rhino 的主要缺点也正是它的强大之处,rhino 是一种轻量级的、功能强大的脚本语言。rhino 使用原型而不是类,这使它比很多脚本语言更适合开发 gui 应用程序,在考虑性能和风格等因素时更是如此。

  rhino语言特点的优缺点

  一方面,作为一种动态类型的、基于原型的脚本语言,rhino借用了很多javascript语法。比如,rhino不再使用语句结束符( ; ),放宽了变量声明规则,并且极大地简化了修改和检索对象属性的语法。另一方面,作为javascript 的java实现,rhino语法非常类似于java编程语言。比如,rhino采用了与 java 编程语言相似的循环和条件结构,并且遵循类似的语法模式来表示这些结构。

  rhino 和 java 语言之间有一些显著的区别。rhino 是一种基于原型的(prototype-based)语言,而不是一种基于类的(class-based)语言。rhino中,函数和变量的声明中看不到类型,取而代之的是,使用 function关键字声明函数,使用 var关键字声明局部变量。

  rhino的原始想法是将javascript 编译成java字节码执行,即采用编译执行的方式。由于由于jvm存在垃圾收集、编译和装载过程的开销过大等限制,rhino采用了解释执行的方式。

  如何下载rhino安装包

  用户可以从官网http://www.mozilla.org/rhino/ 下载rhino,笔者下载的版本为rhino1.7r3.zip。

  其中,主要的目录与文件的如下:

  src:rhino相关jar包对应的源代码

  javadoc:rhino相关jar包对应的java说明文档

  examples:rhino相关示例

  build.xml:rhino工程对应的ant文件

  js.jar:rhino对应的jar包

  rhino环境配置

  在使用之前,我们需要配置环境及运行js脚本。具体如下:

  1、将下载包中的js.jar文件加入系统classpath中。

  2、运行js解释器java org.mozilla.javascript.tools.shell.main。进入交互模式:

rhino 1.7 release 3 2011 05 09

js>

  注:第一行为js解释器的版本号,后面跟着提示符 js>

  下面我们将利用js shell,使用javascript操纵java对象。

javascript操纵java对象

  1、rhino如何访问java包与类文件

  java语法规定,任何代码都必须以class文件的形式存在,而每个class文件必须属于一个package,默认为default。而javascript并没有类似package的层级结构概念,那么如何使用rhino访问java类文件呢?

  rhino定义了一个top-level变量packages。变量packages对应的所有属性均对应java包名。比如,我们需要访问某一个java的package com.example。

js> packages.com.example

[javapackage com.example]

  简单起见,我们也可以去掉变量packages,直接输入java包名。因此,上述package com.example等价与com.example,如下:

js> com.example

  刚才演示了如何通过js shell访问java包,访问java类的方式类似。假如我们需要访问标准的java 文件类java.io.file,如下。

 js> java.io.file

[javaclass java.io.file]

  或者,为避免输入全名,我们先导入包,然后输入class类名,如下:

js> importpackage(java.io)

js> file

  这里的importpackage(java.io),在效果上等价于java声明import java.io.*; 不同的是,java会隐式import java.lang.*,而rhino不会。因为rhino定义的对象boolean, math, number, object, string等与java语法完全不同,两者无法等价。

  这里需要注意的是,rhino对该语法的错误处理机制,当被访问的类存在时,rhino加载该class,而当其不存在时,则把它当成package名称,而并不会报错。例如,当访问一个不存在的类com.example.aaa时,输入如下。

js> com.example.aaa

[javapackage com.example.aaa]

  仅当访问类aaa时,rhino才会报错。

  2、rhino如何与java对象交互

  与java类似,rhino使用new操作符创建对象。

js> new java.util.date()

thu nov 03 16:19:04 cst 2011

  可以使用javascript变量存储java对象,并调用其方法,如下:

js> f = new java.io.file("sample.txt")  

 sample.txt  

 js> f.isdirectory()  

 false

  对于static方法与变量,调用如下:

js> java.lang.math.pi

  3.141592653589793

js> java.lang.math.cos(0)

  1

  在javascript中,方法本身就是对象,这一点与java不同。我们可以通过下列方式查看方法的重载:

js> f.listfiles  

function listfiles() {/*  

java.io.file[] listfiles()  

java.io.file[] listfiles(java.io.filenamefilter)  

java.io.file[] listfiles(java.io.filefilter)  

*/}

 输出中列出三个重载方法。第一个为无参函数,第二与第三个对应的参数分别为filenamefilter与filefilter。

  另一个比较有意思的特点是通过构造for..in,查看对象对应的所有方法与变量。如下:

js> for (i in f) { print(i) }  

exists  

parentfile  

mkdir  

tostring  

wait  

[44 others]

  这里列出的方法一部分来自于父类,比如wait来自父类java.lang.object。

  对于javabean,rhino也提供按名字访问的简单方式。比如,通过下面这种方式,我们就可以调用file对象的getname与isdirectory方法:

 js> f.name  

 test.txt  

js> f.directory  

  3、rhino如何实现java接口

  javascript当中,方法本身就是对象。下面我们通过javascript语法{propertyname: value}声明一个javascript方法,并调用该方法如下:

 js> obj = { run: function () { print("\nrunning"); } }  

 [object object]  

 js> obj.run()  

 running

  现在我们构造一个javascript对象,实现runnable接口。并将该对象作为参数,构造一个新的线程,并启动该线程。

 js> r = new java.lang.runnable(obj);  

adapter1@291aff  

js> t = new java.lang.thread(r)  

thread[thread-0,5,main]  

js> t.start()  

js> 

running

  最后的js>提示符与新线程的打印输出running的先后顺序是随机的,取决于线程的调度策略。

  从后端的处理流程来讲,rhino首先为runnable接口的实现类生成java字节码文件。然后调用javascript对象定义的run方法。

  4、rhino如何创建java 数组

  rhino使用java的发射机制生成数组。下面是生成2个string对象的代码:

js> array = java.lang.reflect.array.newinstance(java.lang.string, 2);  

[ljava.lang.string;@a20892  

js> array[0] = "double"  

double  

js> array[1] = "life"  

life  

js> array[0] + array[1]  

doublelife  

 js>

  5、rhino如何捕获与处理异常

  与java类似,rhino使用try...catch关键字处理异常。

js> function classforname(name) {  

try {  

return java.lang.class.forname(name);  

} catch (e if e.javaexception instanceof java.lang.classnotfoundexception) {  

print("class " + name + " not found");  

} catch (e if e.javaexception instanceof java.lang.nullpointerexception) {  

print("class name is null");  

}  

} > > > > > > > > 

js> classforname("nonexistingclass");  

class nonexistingclass not found  

js> classforname(null);  

class name is null

 6、rhino如何调用js文件

  当然,除了在命令行的方式,我们还可以使用操纵javascript文件。下面是一段javascript代码,主要目的是判断该数是否为质数。代码如下:

function isprime (num)  

{  

if (num <= 1) {  

print("enter an integer no less than 2.")  

return false  

var prime = true 

var sqrroot = math.round(math.sqrt(num))  

for (var n = 2; prime & n <= sqrroot; ++n) {  

prime = (num % n != 0)  

return prime  

}

  我们保存文件为c:\isprime.js。然后我们需要调用load方法加载该脚本。最后,我们可以调用isprime方法来判断是否为质数。

js> load("c:/isprime.js")  

js> isprime(33);  

false  

js> isprime(31)  

true

  需要注意的是,注意:文件分隔符需要调整,是“/”而不是“\”。

  上述部分示例可以参见rhino官方网站。另外examples目录下很多例子都值得参考与学习。

  刚才使用javascript操纵java对象。接下来我们看看如何使用java程序访问javascript

  java对象操纵javascript

  下面是一段java代码,用来运行数学表达式。代码如下:

package com.example;  

import sun.org.mozilla.javascript.internal.context;  

import sun.org.mozilla.javascript.internal.scriptable;  

publicclass test {  

publicstaticvoid main(string[] args) {  

context cx = context.enter();  

scriptable scope = cx.initstandardobjects();  

string str = "3/(1+2)";  

object result = cx.evaluatestring(scope, str, null, 1, null);  

system.out.println(str + "=" + context.tonumber(result));  

} finally {  

context.exit();  

  运行java com.example.test,输出结果如下:

  3/(1+2)=1.0

  之所以是1.0而不是1,是因为context.tonumber(result)返回的类型为double。另一个值得注意的是,这里import的package属于jdk 6.0。因此,在不需要rhino提供的js.jar,该程序仍能独立运行。

  虽然rhino作为javascript运行时,功能非常强大,但在性能上却无法与其他的javascript运行时(比如google chrome的v8 javascript engine)相提并论。值得注意的是,jruby专家charles oliver nutter也开始参与rhino项目中,以提升rhino javascript运行时的速度,进而实现与v8的竞争。而oracle在对jvm上的javascript改进与优化,我们有理由期待,在未来,新一代javascript运行时nashorn的速度将会得到极大的提升。

本文出自seven的测试人生公众号最新内容请见作者的github页:http://qaseven.github.io/