Java 8新特性之 Nashorn(八恶人-6)
Joe Gage 蓋奇·喬
“First time in my life I made a pretty penny.And, figured I'd come home and spend time with my mothr for Christmas.”
“有生以來(lái)第一次掙了很多錢,于是,我想回家陪陪我媽一起過(guò)圣誕節(jié)”
一、基礎(chǔ)介紹
從JDK 6開(kāi)始,Java就已經(jīng)捆綁了JavaScript引擎,該引擎基于Mozilla的Rhino。該特性允許開(kāi)發(fā)人員將JavaScript代碼嵌入到Java中,甚至從嵌入的JavaScript中調(diào)用Java。此外,它還提供了使用jrunscript從命令行運(yùn)行JavaScript的能力。如果不需要非常好的性能,并且可以接受ECMAScript 3有限的功能集的話,那它相當(dāng)不錯(cuò)了。
從JDK 8開(kāi)始,Nashorn取代Rhino成為Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1規(guī)范以及一些擴(kuò)展。它使用基于JSR 292的新語(yǔ)言特性,其中包含在JDK 7中引入的invokedynamic,將JavaScript編譯成Java字節(jié)碼。
與先前的Rhino實(shí)現(xiàn)相比,這帶來(lái)了2到10倍的性能提升,雖然它仍然比Chrome和Node.js中的V8引擎要差一些。
我們先來(lái)個(gè)例子感覺(jué)一下java中使用JavaScript:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
System.out.println( engine.getClass().getName() );
try {
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
}catch (javax.script.ScriptException e){
e.printStackTrace();
}
輸出如下:
dk.nashorn.api.scripting.NashornScriptEngine Result: 2
與Java相比,使用JavaScript進(jìn)行JavaFX開(kāi)發(fā)會(huì)快很多。
二、在哪里使用JS
Shell腳本
Nashorn引擎可以使用jjs命令從命令行調(diào)用。你可以不帶任何參數(shù)調(diào)用它,這會(huì)將你帶入一個(gè)交互模式,或者你可以傳遞一個(gè)希望執(zhí)行的JavaScript文件名,或者你可以用它作為shell腳本的替代,像這樣:
#!/usr/bin/env jjs
var name = $ARG[0];
print(name ? "Hello, ${name}!" : "Hello, world!");
向jjs傳遞程序參數(shù),需要加“—”前綴。因此舉例來(lái)說(shuō),你可以這樣調(diào)用:
./hello-script.js – Joe
如果沒(méi)有“—”前綴,參數(shù)會(huì)被解釋為文件名。
向Java傳遞數(shù)據(jù)或者從Java傳出數(shù)據(jù)
正如上文所說(shuō)的那樣,你可以從Java代碼直接調(diào)用JavaScript;只需獲取一個(gè)引擎對(duì)象并調(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)部訪問(wèn)的全局變量:
int valueIn = 10;
SimpleBindings simpleBindings = new SimpleBindings();
simpleBindings.put("globalValue", valueIn);
nashorn.eval("print (globalValue)", simpleBindings);
JavaScript eval的求值結(jié)果將會(huì)從引擎的“eval”方法返回:
Integer result = (Integer) nashorn.eval("1 + 2");
assert(result == 3);
在Nashorn中使用Java類
前面已經(jīng)提到,Nashorn最強(qiáng)大的功能之一源于在JavaScript中調(diào)用Java類。你不僅能夠訪問(wèn)類并創(chuàng)建實(shí)例,你還可以繼承他們,調(diào)用他們的靜態(tài)方法,幾乎可以做任何你能在Java中做的事。
作為一個(gè)例子,讓我們看下來(lái)龍去脈。JavaScript沒(méi)有任何語(yǔ)言特性是面向并發(fā)的,所有常見(jiàn)的運(yùn)行時(shí)環(huán)境都是單線程的,或者至少?zèng)]有任何共享狀態(tài)。有趣的是,在Nashorn環(huán)境中,JavaScript確實(shí)可以并發(fā)運(yùn)行,并且有共享狀態(tài),就像在Java中一樣:
// 訪問(wèn)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();
請(qǐng)注意,從Nashorn訪問(wèn)類的規(guī)范做法是使用Java.type,并且可以使用Java.extend擴(kuò)展一個(gè)類。
Nashorn JavaScript特有的方言
正如簡(jiǎn)介部分所提到的那樣,Nashorn支持的JavaScript實(shí)現(xiàn)了ECMAScript 5.1版本及一些擴(kuò)展。我并不建議使用這些擴(kuò)展,因?yàn)樗鼈兗炔皇荍ava,也不是JavaScript,兩類開(kāi)發(fā)人員都會(huì)覺(jué)得它不正常。另一方面,有兩個(gè)擴(kuò)展在整個(gè)Oracle文檔中被大量使用,因此,我們應(yīng)該了解它們。首先,讓我們?yōu)榱私獾谝粋€(gè)擴(kuò)展做些準(zhǔn)備。正如前文所述,開(kāi)發(fā)人員可以使用Java.extend從JavaScript中擴(kuò)展一個(gè)Java類。如果需要繼承一個(gè)抽象Java類或者實(shí)現(xiàn)一個(gè)接口,那么可以使用一種更簡(jiǎn)便的語(yǔ)法。在這種情況下,開(kāi)發(fā)人員實(shí)際上可以調(diào)用抽象類或接口的構(gòu)造函數(shù),并傳入一個(gè)描述方法實(shí)現(xiàn)的JavaScript對(duì)象常量。這種常量不過(guò)是name/value對(duì),你可能了解JSON格式,這與那個(gè)類似。這使我們可以像下面這樣實(shí)現(xiàn)Runnable接口:
var r = new java.lang.Runnable({
run: function() {
print("running...
");
}
});
在這個(gè)例子中,一個(gè)對(duì)象常量指定了run方法的實(shí)現(xiàn),我們實(shí)際上是用它調(diào)用了Runnable的構(gòu)造函數(shù)。注意,這是Nashorn的實(shí)現(xiàn)提供給我們的一種方式,否則,我們無(wú)法在JavaScript這樣做。
示例代碼已經(jīng)與我們?cè)贘ava中以匿名內(nèi)部類實(shí)現(xiàn)接口的方式類似了,但還不完全一樣。這將我們帶到了第一個(gè)擴(kuò)展,它允許開(kāi)發(fā)人員在調(diào)用構(gòu)造函數(shù)時(shí)在右括號(hào)“)”后面?zhèn)鬟f最后一個(gè)參數(shù)。這種做法的代碼如下:
var r = new java.lang.Runnable() {
run: function() {
print("running...
");
}
};
……它實(shí)現(xiàn)了完全相同的功能,但更像Java。
第二個(gè)常用的擴(kuò)展一種函數(shù)的簡(jiǎn)便寫(xiě)法,它允許刪除單行函數(shù)方法體中的兩個(gè)花括號(hào)以及return語(yǔ)句。這樣,上一節(jié)中的例子:
list.forEach(function(el) { print(el) } );
可以表達(dá)的更簡(jiǎn)潔一些:
list.forEach(function(el) print(el));
Avatar.js
我們已經(jīng)看到,有了Nashorn,我們就有了一個(gè)嵌入到Java的優(yōu)秀的JavaScript引擎。我們也已經(jīng)看到,我們可以從Nashorn訪問(wèn)任意Java類。Avatar.js更進(jìn)一步,它“為Java平臺(tái)帶來(lái)了Node編程模型、API和模塊生態(tài)系統(tǒng)”。要了解這意味著什么以及它為什么令人振奮,我們首先必須了解Node是什么。從根本上說(shuō),Node是將Chrome的V8 JavaScript引擎剝離出來(lái),使它可以從命令行運(yùn)行,而不再需要瀏覽器。這樣,JavaScript就不是只能在瀏覽器中運(yùn)行了,而且可以在服務(wù)器端運(yùn)行。在服務(wù)器端以任何有意義的方式運(yùn)行JavaScript都至少需要訪問(wèn)文件系統(tǒng)和網(wǎng)絡(luò)。為了做到這一點(diǎn),Node內(nèi)嵌了一個(gè)名為libnv的庫(kù),以異步方式實(shí)現(xiàn)該項(xiàng)功能。實(shí)際上,這意味著操作系統(tǒng)調(diào)用永遠(yuǎn)不會(huì)阻塞,即使它過(guò)一段時(shí)間才能返回。開(kāi)發(fā)人員需要提供一個(gè)回調(diào)函數(shù)代替阻塞。該函數(shù)會(huì)在調(diào)用完成時(shí)立即觸發(fā),如果有任何結(jié)果就返回。
有若干公司都在重要的應(yīng)用程序中使用了Node,其中包括Walmart和Paypal。
讓我們來(lái)看一個(gè)JavaScript的小例子,它是我根據(jù)Node網(wǎng)站上的例子改寫(xiě)而來(lái):
//加載“http”模塊(這是阻塞的)來(lái)處理http請(qǐng)求
var http = require('http');
//當(dāng)有請(qǐng)求時(shí),返回“Hello,World
”
function handleRequest(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello, World
');
}
//監(jiān)聽(tīng)localhost,端口1337
//并提供回調(diào)函數(shù)handleRequest
//這里體現(xiàn)了其非阻塞/異步特性
http.createServer(handleRequest).listen(1337, '127.0.0.1');
//記錄到控制臺(tái),確保我們?cè)谘刂_的方向前進(jìn)
console.log('Get your hello at http://127.0.0.1:1337/');
要運(yùn)行這段代碼,需要安裝Node,然后將上述JavaScript代碼保存到一個(gè)文件中。最后,將該文件作為一個(gè)參數(shù)調(diào)用Node。
將libuv綁定到Java類,并使JavaScript可以訪問(wèn)它們,Avatar.js旨在以這種方式提供與Node相同的核心API。雖然這可能聽(tīng)上去很繁瑣,但這種方法很有效。Avatar.js支持許多Node模塊。對(duì)Node主流Web框架“express”的支持表明,這種方式確實(shí)適用于許多現(xiàn)有的項(xiàng)目。
令人遺憾的是,在寫(xiě)這篇文章的時(shí)候,還沒(méi)有一個(gè)Avatar.js的二進(jìn)制分發(fā)包。有一個(gè)自述文件說(shuō)明了如何從源代碼進(jìn)行構(gòu)建,但是如果真沒(méi)有那么多時(shí)間從頭開(kāi)始構(gòu)建,那么也可以從這里下載二進(jìn)制文件而不是自行構(gòu)建。兩種方式都可以,但為了更快的得到結(jié)果,我建議選擇第二種方式。
一旦創(chuàng)建了二進(jìn)制文件并放進(jìn)了lib文件夾,就可以使用下面這樣的語(yǔ)句調(diào)用Avatar.js框架:
java -Djava.library.path=lib -jar lib/avatar-js.jar helloWorld.js
假設(shè)演示服務(wù)器(上述代碼)保存到了一個(gè)名為“helloWorld.js”的文件中。
讓我們?cè)賳?wèn)一次,這為什么有用?Oracle的專家(幻燈片10)指出了該庫(kù)的幾個(gè)適用場(chǎng)景。我對(duì)其中的兩點(diǎn)持大致相同的看法,即:
有一個(gè)Node應(yīng)用程序,并希望使用某個(gè)Java庫(kù)作為Node API的補(bǔ)充
希望切換到JavaScript和Node API,但需要將遺留的Java代碼部分或全部嵌入
兩個(gè)應(yīng)用場(chǎng)景都可以通過(guò)使用Avatar.js并從JavaScript代碼中調(diào)用任何需要的Java類來(lái)實(shí)現(xiàn)。我們已經(jīng)看到,Nashorn支持這種做法。
下面我將舉一個(gè)第一個(gè)應(yīng)用場(chǎng)景的例子。JavaScript目前只有一種表示數(shù)值的類型,名為“number”。這相當(dāng)于Java的“double”精度,并且有同樣的限制。JavaScript的number,像Java的double一樣,并不能表示任意的范圍和精度,比如在計(jì)量貨幣時(shí)。
在Java中,我們可以使用BigDecimal,它正是用于此類情況。但JavaScript沒(méi)有內(nèi)置與此等效的類型,因此,我們就可以直接從JavaScript代碼中訪問(wèn)BigDecimal類,安全地處理貨幣值。
讓我們看一個(gè)Web服務(wù)示例,它計(jì)算某個(gè)數(shù)量的百分之幾是多少。首先,需要有一個(gè)函數(shù)執(zhí)行實(shí)際的計(jì)算:
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沒(méi)有類型聲明,除此之外,上述代碼與我針對(duì)該任務(wù)編寫(xiě)的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”來(lái)解析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 + '
');
}
}
我們又使用了Node核心模塊來(lái)處理請(qǐng)求URL,從中解析出查詢參數(shù)amount和percentage。
當(dāng)啟動(dòng)服務(wù)器(如前所述)并使用瀏覽器發(fā)出下面這樣一個(gè)請(qǐng)求時(shí),
http://localhost:1337/calculate? amount=99700000000000000086958613&percentage=7.59
就會(huì)得到正確的結(jié)果“7567230000000000006600158.73”。這在單純使用JavaScript的“number”類型時(shí)是不可能。
當(dāng)你決定將現(xiàn)有的JEE應(yīng)用程序遷移到JavaScript和Node時(shí),第二個(gè)應(yīng)用場(chǎng)景就有意義了。在這種情況下,你很容易就可以從JavaScript代碼內(nèi)訪問(wèn)現(xiàn)有的所有服務(wù)。另一個(gè)相關(guān)的應(yīng)用場(chǎng)景是,在使用JavaScript和Node構(gòu)建新的服務(wù)器功能時(shí),仍然可以受益于現(xiàn)有的JEE服務(wù)。
此外,基于Avatar.js的Avatar項(xiàng)目也朝著相同的方向發(fā)展。該項(xiàng)目的詳細(xì)信息超出了本文的討論范圍,但讀者可以閱讀這份Oracle公告做一個(gè)粗略的了解。該項(xiàng)目的基本思想是,用JavaScript編寫(xiě)應(yīng)用程序,并訪問(wèn)JEE服務(wù)。Avatar項(xiàng)目包含Avatar.js的一個(gè)二進(jìn)制分發(fā)包,但它需要Glassfish用于安裝和開(kāi)發(fā)。
小結(jié)
Nashorn項(xiàng)目增強(qiáng)了JDK 6中原有的Rhino實(shí)現(xiàn),極大地提升了運(yùn)行時(shí)間較長(zhǎng)的應(yīng)用程序的性能,例如用在Web服務(wù)器中的時(shí)候。Nashorn將Java與JavaScript集成,甚至還考慮了JDK 8的新Lambda表達(dá)式。Avatar.js帶來(lái)了真正的創(chuàng)新,它基于這些特性構(gòu)建,并提供了企業(yè)級(jí)Java與JavaScript代碼的集成,同時(shí)在很大程度上與JavaScript服務(wù)器端編程事實(shí)上的標(biāo)準(zhǔn)兼容。
完整實(shí)例以及用于Mac OS X的Avatar.js二進(jìn)制文件可以從Github上下載。
參考鏈接:
http://www.infoq.com/cn/articles/nashorn
圖片來(lái)源:八惡人(movie)
總結(jié)
以上是生活随笔為你收集整理的Java 8新特性之 Nashorn(八恶人-6)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 甲骨文的酱酒和赖茅酒是什么关系?
- 下一篇: 怎么创建具有真实纹理的CG场景岩石?