天天看點

深入淺出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/