javascript
Nashorn——在JDK 8中融合Java与JavaScript之力--转
原文地址:http://www.infoq.com/cn/articles/nashorn
從JDK 6開始,Java就已經(jīng)捆綁了JavaScript引擎,該引擎基于Mozilla的Rhino。該特性允許開發(fā)人員將JavaScript代碼嵌入到Java中,甚至從嵌入的JavaScript中調(diào)用Java。此外,它還提供了使用jrunscript從命令行運(yùn)行JavaScript的能力。如果不需要非常好的性能,并且可以接受ECMAScript 3有限的功能集的話,那它相當(dāng)不錯了。
從JDK 8開始,Nashorn取代Rhino成為Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1規(guī)范以及一些擴(kuò)展。它使用基于JSR 292的新語言特性,其中包含在JDK 7中引入的invokedynamic,將JavaScript編譯成Java字節(jié)碼。
與先前的Rhino實(shí)現(xiàn)相比,這帶來了2到10倍的性能提升,雖然它仍然比Chrome和Node.js中的V8引擎要差一些。如果你對實(shí)現(xiàn)細(xì)節(jié)感興趣,那么可以看看這些來自2013 JVM語言峰會的幻燈片。
由于Nashorn隨JDK 8而來,它還增加了簡潔的函數(shù)式接口支持。接下來,我們很快就會看到更多細(xì)節(jié)。
讓我們從一個小例子開始。首先,你可能需要安裝JDK 8和NetBeans、IntelliJ IDEA或者Eclipse。對于集成JavaScript開發(fā),它們都至少提供了基本的支持。讓我們創(chuàng)建一個簡單的Java項目,其中包含下面兩個示例文件,并運(yùn)行它:
(點(diǎn)擊圖片可以查看大圖)
在第12行,我們使用引擎的“eval”方法對任意JavaScript代碼求值。在本示例中,我們只是加載了上面的JavaScript文件并對其求值。你可能會發(fā)現(xiàn)那個“print”并不熟悉。它不是JavaScript的內(nèi)建函數(shù),而是Nashorn提供的,它還提供了其它方便的、在腳本環(huán)境中大有用武之地的函數(shù)。你也可以將 “hello world”的打印代碼直接嵌入到傳遞給“eval”方法的字符串,但將JavaScript放在它自己的文件中為其開啟了全新的工具世界。
Eclipse目前還沒有對Nashorn提供專門的支持,不過,通過JavaScript開發(fā)工具(JSDT)項目,它已經(jīng)支持JavaScript的基本工具和編輯。
(點(diǎn)擊圖片可以查看大圖)
IntelliJ IDEA?13.1(社區(qū)版和旗艦版)提供了出色的JavaScript和Nashorn支持。它有一個全功能的調(diào)試器,甚至允許在Java和JavaScript之間保持重構(gòu)同步,因此舉例來說,如果你重命名一個被JavaScript引用的Java類,或者重命名一個用于Java源代碼中的JavaScript文件,那么該IDE將跨語言修改相應(yīng)的引用。
下面是一個例子,展示如何調(diào)試從Java調(diào)用的JavaScript(請注意,NetBeans也提供了JavaScript調(diào)試器,如下截圖所示):
(點(diǎn)擊圖片可以查看大圖)
你可能會說,工具看上去不錯,而且新實(shí)現(xiàn)修復(fù)了性能以及一致性問題,但我為什么應(yīng)該用它呢?一個原因是一般的腳本編寫。有時候,能夠直接插入任何類型的字符串,并任由它被解釋,會很方便。有時候,沒有礙事的編譯器,或者不用為靜態(tài)類型擔(dān)心,可能也是不錯的。或者,你可能對Node.js編程模型感興趣,它也可以和Java一起使用,在本文的末尾我們會看到。另外,還有個情況不得不提一下,與Java相比,使用JavaScript進(jìn)行JavaFX開發(fā)會快很多。
Shell腳本
Nashorn引擎可以使用jjs命令從命令行調(diào)用。你可以不帶任何參數(shù)調(diào)用它,這會將你帶入一個交互模式,或者你可以傳遞一個希望執(zhí)行的JavaScript文件名,或者你可以用它作為shell腳本的替代,像這樣:
#!/usr/bin/env jjs var name = $ARG[0]; print(name ? "Hello, ${name}!" : "Hello, world!");向jjs傳遞程序參數(shù),需要加“—”前綴。因此舉例來說,你可以這樣調(diào)用:
./hello-script.js – Joe如果沒有“—”前綴,參數(shù)會被解釋為文件名。
向Java傳遞數(shù)據(jù)或者從Java傳出數(shù)據(jù)
正如上文所說的那樣,你可以從Java代碼直接調(diào)用JavaScript;只需獲取一個引擎對象并調(diào)用它的“eval”方法。你可以將數(shù)據(jù)作為字符串顯式傳遞……
ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn"); String name = "Olli"; nashorn.eval("print('" + name + "')");……或者你可以在Java中傳遞綁定,它們是可以從JavaScript引擎內(nèi)部訪問的全局變量:
int valueIn = 10; SimpleBindings simpleBindings = new SimpleBindings(); simpleBindings.put("globalValue", valueIn); nashorn.eval("print (globalValue)", simpleBindings);JavaScript eval的求值結(jié)果將會從引擎的“eval”方法返回:
Integer result = (Integer) nashorn.eval("1 + 2"); assert(result == 3);在Nashorn中使用Java類
前面已經(jīng)提到,Nashorn最強(qiáng)大的功能之一源于在JavaScript中調(diào)用Java類。你不僅能夠訪問類并創(chuàng)建實(shí)例,你還可以繼承他們,調(diào)用他們的靜態(tài)方法,幾乎可以做任何你能在Java中做的事。
作為一個例子,讓我們看下來龍去脈。JavaScript沒有任何語言特性是面向并發(fā)的,所有常見的運(yùn)行時環(huán)境都是單線程的,或者至少沒有任何共享狀態(tài)。有趣的是,在Nashorn環(huán)境中,JavaScript確實(shí)可以并發(fā)運(yùn)行,并且有共享狀態(tài),就像在Java中一樣:
// 訪問Java類Thread var Thread = Java.type("java.lang.Thread"); // 帶有run方法的子類 var MyThread = Java.extend(Thread, { run: function() { print("Run in separate thread"); } }); var th = new MyThread(); th.start(); th.join();請注意,從Nashorn訪問類的規(guī)范做法是使用Java.type,并且可以使用Java.extend擴(kuò)展一個類。
令人高興的函數(shù)式
從各方面來說,隨著JDK 8的發(fā)布,Java——至少在某種程度上——已經(jīng)變成一種函數(shù)式語言。開發(fā)人員可以在集合上使用高階函數(shù),比如,遍歷所有的元素。高階函數(shù)是把另一個函數(shù)當(dāng)作參數(shù)的函數(shù),它可以用這個函數(shù)參數(shù)做些有意義的事情。請看下面Java中高階函數(shù)的示例:
List<Integer> list = Arrays.asList(3, 4, 1, 2); list.forEach(new Consumer() { @Override public void accept(Object o) { System.out.println(o); } });對于這個例子,我們的傳統(tǒng)實(shí)現(xiàn)方式是使用一個“外部”循環(huán)遍歷元素,但現(xiàn)在,我們沒有那樣做,而是將一個“Consumer”函數(shù)傳遞給了“forEach”操作,一個高階的“內(nèi)部循環(huán)”操作會將集合中的每個元素一個一個地傳遞給Consumer的“accept”方法并執(zhí)行它。
如上所述,對于這樣的高階函數(shù),函數(shù)式語言的做法是接收一個函數(shù)參數(shù),而不是一個對象。雖然在傳統(tǒng)上講,傳遞函數(shù)引用本身超出了Java的范圍,但現(xiàn)在,JDK 8有一些語法糖,使它可以使用Lambda表達(dá)式(又稱為“閉包”)來實(shí)現(xiàn)那種表示方式。例如:
List<Integer> list = Arrays.asList(3, 4, 1, 2); list.forEach(el -> System.out.println(el));在這種情況下,“forEach”的參數(shù)是這樣一個函數(shù)引用的形式。這是可行的,因?yàn)镃ustomer是一個函數(shù)式接口(有時稱為“單一抽象方法(Single Abstract Method)”類型或“SAM”)。
那么,我們?yōu)槭裁匆谟懻揘ashorn時談?wù)揕ambda表達(dá)式呢?因?yàn)樵贘avaScript中,開發(fā)人員也可以這樣編寫代碼,而在這種情況下,Nashorn可以特別好地縮小Java和JavaScript之間的差距。尤其是,它甚至允許開發(fā)人員將純JavaScript函數(shù)作為函數(shù)式接口(SAM類型)的實(shí)現(xiàn)來傳遞。
讓我們來看一些純JavaScript代碼,它們與上述Java代碼實(shí)現(xiàn)一樣的功能。注意,在JavaScript中沒有內(nèi)置的列表類型,只有數(shù)組;不過這些數(shù)組的大小是動態(tài)分配的,而且有與Java列表類似的方法。因此,在這個例子中,我們調(diào)用一個JavaScript數(shù)組的“for Each”方法:
var jsArray = [4,1,3,2]; jsArray.forEach(function(el) { print(el) } );相似之處顯而易見;但那還不是全部。開發(fā)人員還可以將這樣一個JavaScript數(shù)組轉(zhuǎn)換成一個Java列表:
var list = java.util.Arrays.asList(jsArray);看見了嗎?是的,這就是在Nashorn中運(yùn)行的JavaScript。既然它現(xiàn)在是一個Java列表,那么開發(fā)人員就可以調(diào)用其“forEach”方法。注意,這個“forEach”方法不同于我們在JavaScript數(shù)組上調(diào)用的那個,它是定義在java集合上的“forEach”方法。這里,我們?nèi)匀粋鬟f一個純JavaScript函數(shù):
list.forEach(function(el) { print(el) } );Nashorn允許開發(fā)人員在需要使用函數(shù)式接口(SAM類型)的地方提供純JavaScript函數(shù)引用。這不僅適應(yīng)于Java,也適應(yīng)于JavaScript。
ECMAScript的下一個版本——預(yù)計是今年的最后一個版本——將包含函數(shù)的短語法,允許開發(fā)人員將函數(shù)寫成近似Java Lambda表達(dá)式的形式,只不過它使用雙箭頭=>。這進(jìn)一步增強(qiáng)了一致性。
Nashorn JavaScript特有的方言
正如簡介部分所提到的那樣,Nashorn支持的JavaScript實(shí)現(xiàn)了ECMAScript 5.1版本及一些擴(kuò)展。我并不建議使用這些擴(kuò)展,因?yàn)樗鼈兗炔皇荍ava,也不是JavaScript,兩類開發(fā)人員都會覺得它不正常。另一方面,有兩個擴(kuò)展在整個Oracle文檔中被大量使用,因此,我們應(yīng)該了解它們。首先,讓我們?yōu)榱私獾谝粋€擴(kuò)展做些準(zhǔn)備。正如前文所述,開發(fā)人員可以使用Java.extend從JavaScript中擴(kuò)展一個Java類。如果需要繼承一個抽象Java類或者實(shí)現(xiàn)一個接口,那么可以使用一種更簡便的語法。在這種情況下,開發(fā)人員實(shí)際上可以調(diào)用抽象類或接口的構(gòu)造函數(shù),并傳入一個描述方法實(shí)現(xiàn)的JavaScript對象常量。這種常量不過是name/value對,你可能了解JSON格式,這與那個類似。這使我們可以像下面這樣實(shí)現(xiàn)Runnable接口:
var r = new java.lang.Runnable({run: function() {print("running...\n");} });在這個例子中,一個對象常量指定了run方法的實(shí)現(xiàn),我們實(shí)際上是用它調(diào)用了Runnable的構(gòu)造函數(shù)。注意,這是Nashorn的實(shí)現(xiàn)提供給我們的一種方式,否則,我們無法在JavaScript這樣做。
示例代碼已經(jīng)與我們在Java中以匿名內(nèi)部類實(shí)現(xiàn)接口的方式類似了,但還不完全一樣。這將我們帶到了第一個擴(kuò)展,它允許開發(fā)人員在調(diào)用構(gòu)造函數(shù)時在右括號“)”后面?zhèn)鬟f最后一個參數(shù)。這種做法的代碼如下:
var r = new java.lang.Runnable() {run: function() {print("running...\n");} };……它實(shí)現(xiàn)了完全相同的功能,但更像Java。
第二個常用的擴(kuò)展一種函數(shù)的簡便寫法,它允許刪除單行函數(shù)方法體中的兩個花括號以及return語句。這樣,上一節(jié)中的例子:
list.forEach(function(el) { print(el) } );可以表達(dá)的更簡潔一些:
list.forEach(function(el) print(el));Avatar.js
我們已經(jīng)看到,有了Nashorn,我們就有了一個嵌入到Java的優(yōu)秀的JavaScript引擎。我們也已經(jīng)看到,我們可以從Nashorn訪問任意Java類。Avatar.js更進(jìn)一步,它“為Java平臺帶來了Node編程模型、API和模塊生態(tài)系統(tǒng)”。要了解這意味著什么以及它為什么令人振奮,我們首先必須了解Node是什么。從根本上說,Node是將Chrome的V8 JavaScript引擎剝離出來,使它可以從命令行運(yùn)行,而不再需要瀏覽器。這樣,JavaScript就不是只能在瀏覽器中運(yùn)行了,而且可以在服務(wù)器端運(yùn)行。在服務(wù)器端以任何有意義的方式運(yùn)行JavaScript都至少需要訪問文件系統(tǒng)和網(wǎng)絡(luò)。為了做到這一點(diǎn),Node內(nèi)嵌了一個名為libnv的庫,以異步方式實(shí)現(xiàn)該項功能。實(shí)際上,這意味著操作系統(tǒng)調(diào)用永遠(yuǎn)不會阻塞,即使它過一段時間才能返回。開發(fā)人員需要提供一個回調(diào)函數(shù)代替阻塞。該函數(shù)會在調(diào)用完成時立即觸發(fā),如果有任何結(jié)果就返回。
有若干公司都在重要的應(yīng)用程序中使用了Node,其中包括Walmart和Paypal。
讓我們來看一個JavaScript的小例子,它是我根據(jù)Node網(wǎng)站上的例子改寫而來:
//加載“http”模塊(這是阻塞的)來處理http請求 var http = require('http'); //當(dāng)有請求時,返回“Hello,World\n” function handleRequest(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello, World\n'); } //監(jiān)聽localhost,端口1337 //并提供回調(diào)函數(shù)handleRequest //這里體現(xiàn)了其非阻塞/異步特性 http.createServer(handleRequest).listen(1337, '127.0.0.1'); //記錄到控制臺,確保我們在沿著正確的方向前進(jìn) console.log('Get your hello at http://127.0.0.1:1337/');要運(yùn)行這段代碼,需要安裝Node,然后將上述JavaScript代碼保存到一個文件中。最后,將該文件作為一個參數(shù)調(diào)用Node。
將libuv綁定到Java類,并使JavaScript可以訪問它們,Avatar.js旨在以這種方式提供與Node相同的核心API。雖然這可能聽上去很繁瑣,但這種方法很有效。Avatar.js支持許多Node模塊。對Node主流Web框架“express”的支持表明,這種方式確實(shí)適用于許多現(xiàn)有的項目。
令人遺憾的是,在寫這篇文章的時候,還沒有一個Avatar.js的二進(jìn)制分發(fā)包。有一個自述文件說明了如何從源代碼進(jìn)行構(gòu)建,但是如果真沒有那么多時間從頭開始構(gòu)建,那么也可以從這里下載二進(jìn)制文件而不是自行構(gòu)建。兩種方式都可以,但為了更快的得到結(jié)果,我建議選擇第二種方式。
一旦創(chuàng)建了二進(jìn)制文件并放進(jìn)了lib文件夾,就可以使用下面這樣的語句調(diào)用Avatar.js框架:
java -Djava.library.path=lib -jar lib/avatar-js.jar helloWorld.js
假設(shè)演示服務(wù)器(上述代碼)保存到了一個名為“helloWorld.js”的文件中。
讓我們再問一次,這為什么有用?Oracle的專家(幻燈片10)指出了該庫的幾個適用場景。我對其中的兩點(diǎn)持大致相同的看法,即:
兩個應(yīng)用場景都可以通過使用Avatar.js并從JavaScript代碼中調(diào)用任何需要的Java類來實(shí)現(xiàn)。我們已經(jīng)看到,Nashorn支持這種做法。
下面我將舉一個第一個應(yīng)用場景的例子。JavaScript目前只有一種表示數(shù)值的類型,名為“number”。這相當(dāng)于Java的“double”精度,并且有同樣的限制。JavaScript的number,像Java的double一樣,并不能表示任意的范圍和精度,比如在計量貨幣時。
在Java中,我們可以使用BigDecimal,它正是用于此類情況。但JavaScript沒有內(nèi)置與此等效的類型,因此,我們就可以直接從JavaScript代碼中訪問BigDecimal類,安全地處理貨幣值。
讓我們看一個Web服務(wù)示例,它計算某個數(shù)量的百分之幾是多少。首先,需要有一個函數(shù)執(zhí)行實(shí)際的計算:
var BigDecimal = Java.type('java.math.BigDecimal'); function calculatePercentage(amount, percentage) { var result = new BigDecimal(amount).multiply( new BigDecimal(percentage)).divide( new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN); return result.toPlainString(); }JavaScript沒有類型聲明,除此之外,上述代碼與我針對該任務(wù)編寫的Java代碼非常像:
public static String calculate(String amount, String percentage) { BigDecimal result = new BigDecimal(amount).multiply( new BigDecimal(percentage)).divide( new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN); return result.toPlainString(); }我們只需要替換上文Node示例中的handleRequest函數(shù)就可以完成代碼。替換后的代碼如下:
//加載工具模塊“url”來解析url var url = require('url'); function handleRequest(req, res) { // '/calculate' Web服務(wù)地址 if (url.parse(req.url).pathname === '/calculate') { var query = url.parse(req.url, true).query; //數(shù)量和百分比作為查詢參數(shù)傳入var result = calculatePercentage(query.amount,query.percentage); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(result + '\n'); } }我們又使用了Node核心模塊來處理請求URL,從中解析出查詢參數(shù)amount和percentage。
當(dāng)啟動服務(wù)器(如前所述)并使用瀏覽器發(fā)出下面這樣一個請求時,
http://localhost:1337/calculate? amount=99700000000000000086958613&percentage=7.59就會得到正確的結(jié)果“7567230000000000006600158.73”。這在單純使用JavaScript的“number”類型時是不可能。
當(dāng)你決定將現(xiàn)有的JEE應(yīng)用程序遷移到JavaScript和Node時,第二個應(yīng)用場景就有意義了。在這種情況下,你很容易就可以從JavaScript代碼內(nèi)訪問現(xiàn)有的所有服務(wù)。另一個相關(guān)的應(yīng)用場景是,在使用JavaScript和Node構(gòu)建新的服務(wù)器功能時,仍然可以受益于現(xiàn)有的JEE服務(wù)。
此外,基于Avatar.js的Avatar項目也朝著相同的方向發(fā)展。該項目的詳細(xì)信息超出了本文的討論范圍,但讀者可以閱讀這份Oracle公告做一個粗略的了解。該項目的基本思想是,用JavaScript編寫應(yīng)用程序,并訪問JEE服務(wù)。Avatar項目包含Avatar.js的一個二進(jìn)制分發(fā)包,但它需要Glassfish用于安裝和開發(fā)。
小結(jié)
Nashorn項目增強(qiáng)了JDK 6中原有的Rhino實(shí)現(xiàn),極大地提升了運(yùn)行時間較長的應(yīng)用程序的性能,例如用在Web服務(wù)器中的時候。Nashorn將Java與JavaScript集成,甚至還考慮了JDK 8的新Lambda表達(dá)式。Avatar.js帶來了真正的創(chuàng)新,它基于這些特性構(gòu)建,并提供了企業(yè)級Java與JavaScript代碼的集成,同時在很大程度上與JavaScript服務(wù)器端編程事實(shí)上的標(biāo)準(zhǔn)兼容。
完整實(shí)例以及用于Mac OS X的Avatar.js二進(jìn)制文件可以從Github上下載。
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/6905572.html
總結(jié)
以上是生活随笔為你收集整理的Nashorn——在JDK 8中融合Java与JavaScript之力--转的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 魅族大数据之流平台设计部署实践--转
- 下一篇: 分布式配置管理平台Disconf--转