摘要即將發(fā)布的Java6.0包含了Java平臺腳本(JSR 223)的實(shí)現。這個(gè)JSR關(guān)注于程序設計語(yǔ)言以及他們與Java的整合。本文通過(guò)一個(gè)簡(jiǎn)單的“Boolean語(yǔ)言”的實(shí)現展示了JSR223的能力和潛力。通過(guò)這個(gè)例子,你將看到如何使用ScriptingAPI(javax.script.*)編寫(xiě)程序,如何打包并發(fā)布一個(gè)符合腳本引擎發(fā)現機制的語(yǔ)言實(shí)現,以及如何使你的腳本引擎對JSR223是可編譯和可調用的。
在Java平臺腳本(JSR 223)——以及他的前任BSF(Bean ScriptingFramework)——之前,已經(jīng)有很多種語(yǔ)言可以與Java交互。其中一些可以接受一段Java程序的文本代碼作為輸入,然后將代碼的執行結果返回給Java程序。而另外一些可以保持Java程序里的對象的引用,并可以執行這個(gè)對象的方法,或者創(chuàng )建一個(gè)新的JavaClass的實(shí)例。由于每個(gè)語(yǔ)言都有自己的與Java交互的方法,開(kāi)發(fā)人員如果想要在他們的Java程序中使用腳本引擎就必須學(xué)習每個(gè)腳本引擎特殊的編程接口。
為了解決這個(gè)問(wèn)題,JSR223定義了一個(gè)約定,所有遵循這個(gè)規范的腳本引擎都必須遵守這個(gè)約定。這個(gè)約定由一組Java接口和類(lèi)、以及一個(gè)打包和部署腳本引擎的機制組成。當使用遵循JSR 223的腳本引擎時(shí),只需要使用標準定義的一組接口。由于腳本引擎的具體實(shí)現會(huì )被良好地封裝,你根本不需要考慮他們。
JSR 223不僅使腳本引擎使用更簡(jiǎn)單,而且也使腳本引擎的開(kāi)發(fā)更簡(jiǎn)單。如果你設計實(shí)現了一個(gè)程序語(yǔ)言,只需要實(shí)現JSR 223的接口來(lái)包裝(wrap)你的腳本引擎,就可以使你的腳本引擎更容易使用并擁有更多的使用者。
在我們看JSR 223的接口和本文對他們的實(shí)現之前,我先要指出:雖然JSR223的名稱(chēng)和本文的標題里都含有“腳本”這個(gè)詞,但是并不意味著(zhù)會(huì )限制在可以使用JSR223進(jìn)行整合的語(yǔ)言。你可以使用任何你喜歡的語(yǔ)言,并且用一個(gè)遵守JSR223約定的層包裝它。這個(gè)語(yǔ)言可以是面向對象的、函數的、或者符合任何編程范型的程序語(yǔ)言。他可以是強類(lèi)型、弱類(lèi)型或者根本沒(méi)有類(lèi)型限制。事實(shí)上,在寫(xiě)這篇文章前,我已經(jīng)為Scheme(一個(gè)弱類(lèi)型的函數程序語(yǔ)言)實(shí)現了一個(gè)遵循JSR223的包裝器,并已經(jīng)放到了SourceForge上。在這篇文章里,我們使用一個(gè)更簡(jiǎn)單的語(yǔ)言,這樣我們就可以集中精力于JSR223,而不用費神于語(yǔ)言的細節。
不用擔心你是否有自己創(chuàng )建一個(gè)程序語(yǔ)言的經(jīng)歷。這篇文章不討論程序語(yǔ)言,只是討論JSR 223定義的程序語(yǔ)言和Java之間需要遵循的約定。
版權聲明:任何獲得Matrix授權的網(wǎng)站,轉載時(shí)請務(wù)必保留以下作者信息和鏈接作者:Chaur Wu;
niuji原文:
http://www.javaworld.com/javaworld/jw-04-2006/jw-0424-scripting.htmlMatrix:
http://www.matrix.org.cn/resource/article/44/44604_JSR+223.html關(guān)鍵字:JSR 223
BoolScript引擎圖一顯示了我們的示例的各個(gè)部分以及他們是如何相互關(guān)聯(lián)的。這篇文章里的示例定義了一個(gè)簡(jiǎn)單的語(yǔ)言,我稱(chēng)之為BoolScript。我稱(chēng)編譯執行BoolScript代碼的程序為BoolScript引擎。除了編譯和執行BoolScript代碼,BoolScript引擎還實(shí)現了JSR223的約定,是一個(gè)符合JSR 223規范的腳本引擎。如圖所示,Boolscript引擎的代碼打包在boolscript.jar中。
圖一:BoolScript概覽。
在這篇文章里,當我提到JSR 223時(shí)都是指JSR 223的規范,JSR 223框架就是指這個(gè)規范的實(shí)現。這篇文章里使用的JSR223框架已經(jīng)包含于Java Standard Edition 6.0中(JavaSE是sun的J2SE新名字)。我們的例子里包含了一個(gè)使用BoolScript引擎的Java程序,代碼詳見(jiàn)BoolScriptHostApp.java文件。注意圖一展示了Java程序總是通過(guò)JSR 223框架間接的和腳本引擎打交道。
你需要Java SE 6.0 beta和這篇文章的二進(jìn)制文件來(lái)運行這個(gè)示例。我使用的Java SE 6.0的版本是build 77。你可以在java.net下載,你也可以使用Sun Developer Network提供的Java SE 6.0。
示例代碼在資源中提供下載,其中包含了以下文件:
+BoolScriptEngine-Source.zip BoolScript引擎的源代碼
+BoolScriptHostExample-Source.zip Java示例程序源代碼
+BoolScriptHostExample.zip BoolScript引擎和Java示例程序的二進(jìn)制代碼
示例程序是BoolScriptHostExample.zip 中的BoolScriptHostApp.class,解壓縮到任意文件夾執行即可。這個(gè)zip文件里還包含了3個(gè)jar文件,執行時(shí)需要將他們加到Java的classpath中。具體可參照run.bat文件里的代碼,這個(gè)文件也包含在BoolScriptHostExample.zip 文件里。示例執行產(chǎn)生如下輸出:
Mozilla Rhino
Bool Script Engine
answer of boolean expression is: false
answer of boolean expression is: true
answer of boolean expression is: false BoolScript 語(yǔ)言在深入JSR 223的細節前,我們先快速地了解一下BoolScript語(yǔ)言。BoolScript非常簡(jiǎn)單,他唯一能做的就是計算布爾表達式的值。下面是BoolScript代碼的例子:
(True | False) & True
(True & x) | y
可以看到,BoolScript支持兩個(gè)操作符:&(邏輯與)和|(邏輯或)。BoolScript還支持三個(gè)操作數:True、False和變量,變量的值只能是True或False。
腳本引擎發(fā)現機制為了看清楚JSR223框架在Java程序和腳本引擎間做了哪些工作,我們先假設你想要在你的程序里使用一個(gè)腳本引擎。首先,你需要創(chuàng )建腳本引擎的實(shí)例;然后,需要將腳本代碼傳給引擎,讓引擎求值(或者編譯這段腳本代碼供以后執行)。我們仔細看下這些步驟,記?。翰徽撐覀冏鍪裁?,我們只能通過(guò)JSR 223框架使用腳本。
創(chuàng )建腳本引擎的實(shí)例前我們首先要創(chuàng )建javax.script.ScriptEngineManager的實(shí)例,然后用這個(gè)實(shí)例查詢(xún)腳本引擎的實(shí)例。你可以根據腳本引擎的名字、MIME類(lèi)型或文件擴展名查詢(xún)引擎實(shí)例。例如我們的BoolScript代碼保存文件是*.bool,那么文件擴展名就是bool。下面的代碼演示了如何使用擴展名查詢(xún)腳本引擎實(shí)例。
ScriptEngineManager engineMgr = new ScriptEngineManager();
ScriptEngine bsEngine = engineMgr.getEngineByExtension("bool");
但是我們在哪指定我們的腳本引擎的名稱(chēng)、MIME類(lèi)型和文件擴展名呢?我們使用了BoolScriptEngineFactory類(lèi)來(lái)指定這些屬性。這個(gè)類(lèi)實(shí)現了javax.script.ScriptEngineFactory接口的getExtensions()、getMimeTypes()、和getNames()方法。我們在這幾個(gè)方法里聲明了BoolScript引擎的名稱(chēng)、MIME類(lèi)型和文件擴展名。下面是BoolScriptEngineFactory的getExtensions()方法:
public List getExtensions()
{
ArrayList<String> extList = new ArrayList<String>();
extList.add("bool");
return extList;
}
你可能會(huì )奇怪為什么使用ScriptEngineManager創(chuàng )建BoolScriptEngine的實(shí)例,而不是用下面的方法直接創(chuàng )建:
ScriptEngine bsEngine = new BoolScriptEngine();
當然,你可以用這種方法創(chuàng )建。事實(shí)上,我在開(kāi)發(fā)示例代碼的時(shí)候為了測試也這樣使用過(guò)。直接創(chuàng )建腳本引擎實(shí)例在測試的時(shí)候是可以的,但在實(shí)際運行環(huán)境中,這樣做違反了必須通過(guò)JSR 223框架操作腳本引擎的規定,破壞了JSR 223隱藏腳本引擎信息的目的。JSR223通過(guò)使用工廠(chǎng)方法(FactoryMethod)模式隱藏腳本引擎的詳細信息,從而達到將腳本引擎從Java程序中解耦的目的。直接創(chuàng )建腳本引擎的另一個(gè)問(wèn)題是略過(guò)了ScriptEngineManager可能會(huì )對腳本引擎實(shí)例做的初始化工作。接下來(lái)我們就會(huì )看到ScriptEngineManager做的這種工作。
ScriptEngineManager是如何根據bool查找BoolScriptEngine并創(chuàng )建他的實(shí)例的呢?答案就是JSR223里的腳本引擎發(fā)現機制。在后面對這個(gè)機制的討論過(guò)程中,你將看到ScriptEngineManager會(huì )對腳本引擎做哪些初始化工作,還有他為什么要做這些工作。
根據腳本引擎發(fā)現機制,一個(gè)腳本引擎的提供者在打包腳本引擎實(shí)現的類(lèi)文件的時(shí)候還需要打包一個(gè)附加的文件到j(luò )ar文件中。這個(gè)文件必須放在jar文件的META-INF/services目錄下面,而且名稱(chēng)必須是javax.script.ScriptEngineFactory。打開(kāi)boolcripts.jar你就可以看到這樣的目錄結構。
文件META-INF/services/javax.script.ScriptEngineFactory的內容必須包含實(shí)現了ScriptEngineFactory的類(lèi)的全名。在我們的例子中只有一個(gè)這樣的類(lèi),我們文件內容如下:
net.sf.model4lang.boolscript.engine.BoolScriptEngineFactory
當一個(gè)腳本引擎提供者將他/她的腳本引擎打包成jar文件并發(fā)布后,用戶(hù)只需要將這個(gè)jar文件放入Java的classpath里就可以完成安裝。圖二展示了Java程序通過(guò)JSR 223框架發(fā)現腳步引擎時(shí)發(fā)生的事件。
圖二 Java程序如何發(fā)現腳本引擎
當使用名字、MIME類(lèi)型或文件擴展名查詢(xún)指定的腳本引擎時(shí),ScriptEngineManager將遍歷classpath中所有的的ScriptEngineFactory類(lèi)。如果找到符合的,ScriptEngineManager就會(huì )創(chuàng )建這個(gè)引擎工廠(chǎng)的實(shí)例,然后用這個(gè)工廠(chǎng)實(shí)例創(chuàng )建腳本引擎的實(shí)例。腳本引擎工廠(chǎng)使用getScriptEngine()創(chuàng )建腳本引擎,腳本引擎提供者需要實(shí)現這個(gè)方法。你可以看下BoolScriptEngineFactory的代碼,其中g(shù)etScriptEngine()的實(shí)現如下:
public ScriptEngine getScriptEngine()
{
return new BoolScriptEngine();
}
這個(gè)方法很簡(jiǎn)單,只是創(chuàng )建了一個(gè)腳本引擎的實(shí)例并將這個(gè)實(shí)例返回給ScriptEngineManager(或者任何調用這個(gè)方法的類(lèi))。我們感興趣的是在ScriptEngineManager得到這個(gè)實(shí)例后,在將這個(gè)實(shí)例返回給Java程序前,ScriptEngineManager為了初始化這個(gè)引擎而調用的setBindings()方法。這時(shí)我們需要了解JSR223的一個(gè)核心概念:Java綁定。在我解釋了綁定、范圍和上下文的概念和構造后,你就能明白setBindings()為腳本引擎做了哪些初始化工作。
綁定、范圍、上下文回憶下,BoolScript語(yǔ)言允許使用下面的代碼:
(True & x) | y
但是他未提供任何構造讓你給變量x、y賦值。我應該將語(yǔ)言設計成可以使用如下的代碼:
x = True
y = False
(True & x) | y
但是我是故意忽略了賦值操作符=,使BoolScript代碼必須在包含定義了變量的值的上下文中執行。這意味著(zhù)當Java程序用BoolScript腳本引擎計算一段文本代碼的值時(shí),同時(shí)需要將一個(gè)上下文提供給腳步引擎,或者至少要讓腳本引擎知道需要使用哪個(gè)上下文。
你可以認為上下文是Java程序和腳本引擎交換數據的袋子。JSR223用接口javax.script.ScriptContext定義上下文這個(gè)結構。如果我們向這個(gè)袋子里放入很多而又不加以組織的話(huà),這個(gè)袋子就會(huì )變得非常凌亂。因此腳本上下文(例如ScriptContext的實(shí)例)將自己的數據劃分到了多個(gè)范圍里。JSR223用接口javax.script.Bindings定義范圍這個(gè)結構。圖三展示了上下文、上下文的范圍、以及范圍中存放的數據。
圖三 腳本引擎管理器和腳本引擎中的上下文和范圍。
圖三里有些信息十分重要:
1. 一個(gè)腳本引擎包含一個(gè)腳本上下文。
2. 一個(gè)腳本引擎管理器(例如ScriptEngineManager的實(shí)例)都可以用來(lái)創(chuàng )建多個(gè)腳本引擎。
3. 腳本引擎管理器包含一個(gè)被稱(chēng)為global scope的全局范圍,但是不包含上下文。
4. 每個(gè)范圍基本上就是一個(gè)名稱(chēng)-值對的集合。圖三中可以看到有個(gè)一個(gè)范圍里包含兩個(gè)這樣的名稱(chēng)-值對,一個(gè)的名稱(chēng)是x,另一個(gè)的名稱(chēng)是y。要注意每個(gè)范圍都是javas.script.Bindings的一個(gè)實(shí)例。
5. 腳本引擎里的上下文包含了一個(gè)全局范圍、一個(gè)引擎范圍和0個(gè)或多個(gè)其他范圍。
6. 一個(gè)腳本引擎可以用來(lái)執行多個(gè)腳本(例如:腳本語(yǔ)言編寫(xiě)的多段單獨的腳本片斷)。
但是圖三種的全局范圍和引擎范圍是什么呢?全局范圍是一個(gè)在多個(gè)腳本引擎中共享的范圍。如果你想在多個(gè)腳本引擎中共享一部分數據,那就可以將這些數據放在全局范圍中。注意全局范圍并不是對所有的腳本引擎來(lái)說(shuō)是全局的。它只對那些被它所在的腳本引擎管理器創(chuàng )建的腳步引起來(lái)說(shuō)是全局的。
引擎范圍是一個(gè)被多個(gè)腳本貢享的范圍。如果你想要在多個(gè)腳本中共享數據,那就可以將這些數據放在引擎范圍中。例如,假設我們有下面兩段腳本:
(True & x) | y //Script A
(True & x) //Script B
如果我們要在這兩個(gè)腳本中共享x的值,我們可以把這個(gè)值放入執行腳本的腳本引擎包含的引擎范圍中。假設現在我們只想在腳本A中保存y的值,那我們就需要創(chuàng )建一個(gè)范圍,記住這個(gè)范圍只對腳本A可見(jiàn),然后將y的值放入這個(gè)范圍中。
作為例子,BoolScriptHostApp.java的main方法里用下面的代碼計算了(x & y):
//bsEngine is an instance of ScriptEngine
bsEngine.put("x", BoolTermEvaluator.tTrue);
bsEngine.put("y", BoolTermEvaluator.tTrue);
bsEngine.eval("x & y\n\n");
這段代碼先將x和y的值放入引擎范圍內,然后調用引起的eval()方法執行BoolScript代碼。如果你看下ScriptEngine接口,你會(huì )發(fā)現eval()方法有很多擁有不同參數的重載方法。如果像上面那樣使用字符串作為參數調用eval()方法,腳本引擎會(huì )在他的上下文中執行這段代碼。如果不希望在腳本引擎的上下文中執行,那就要在調用eval()時(shí)提供上下文。
在我們的實(shí)現里,eval()的實(shí)際工作由BoolTermEvaluator里面的下面這段代碼執行:
public static BoolTerm evaluate(BoolTerm term, ScriptContext context)
{
...
else if (term instanceof Var)
{
Var var = (Var) term;
Bindings bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
if (!bindings.containsKey(var.getName()))
throw new IllegalArgumentException("Variable " + var.getName() + " not set.");
Boolean varValue = (Boolean) bindings.get(var.getName());
if (varValue == Boolean.TRUE)
return BoolTermEvaluator.tTrue;
else
return BoolTermEvaluator.tFalse;
}
...
}
這個(gè)方法通過(guò)計算Term(True、False、或變量)來(lái)執行BoolScript代碼。當Term像上面的代碼那樣是一個(gè)變量的時(shí)候,方法將調用作為參數傳入的上下文的getBindings()方法得到引擎范圍的引用。由于一個(gè)上下文里可以有多個(gè)范圍,所以我們使用ScriptContex.ENGINE_SCOPE表示我們想要得到的是引擎范圍。在得到引擎范圍后,我們使用變量名在引擎范圍中查找變量的值。如果未找到變量的值,則拋出異常。反之,我們將計算這個(gè)變量然后返回他的值。
最后,我準備解釋為什么腳本引擎管理器初始化腳本引擎的時(shí)候要調用腳本引擎的setBindings()方法:當腳本引擎管理器調用引擎的setBindings()方法的時(shí)候,它將自己的全局范圍作為參數傳遞給這個(gè)方法。引擎則將這個(gè)全局范圍存入自己的上下文中。
在結束這個(gè)章節前,讓我們看一下腳本API里的幾個(gè)類(lèi)。前面說(shuō)過(guò)ScriptEngineManager包含了一個(gè)Bindings的實(shí)例作為全局范圍。如果你看一下javax.script.ScriptEngineManager的源代碼,你可以發(fā)現有一個(gè)getBindings()方法用于得到ScriptEngineManager里的Bindings,同樣,還存在一個(gè)setBindings()用于設置ScriptEngineManager的Bindings。
和ScriptEngineManager相似,ScriptEngine包含了一個(gè)ScriptContext的實(shí)例。在接口javax.script.ScriptEngine里也對應存在一個(gè)getContext()方法和一個(gè)setBindings()方法。
因此你可以很容易地在腳本引擎管理器之間共享全局范圍。你要做的僅僅是調用一個(gè)腳本引擎管理器的getBindings()方法得到它的全局范圍,然后調用另一個(gè)的setBindings()方法設置這個(gè)全局范圍。
如果你看了示例里BoolScriptEngine的代碼,你會(huì )發(fā)現里面并沒(méi)有ScriptContext的引用。這是因為BoolScriptEngine繼承了AbstractScriptEngine,而AbstractScriptEngine里有一個(gè)成員是ScriptContext的實(shí)例。如果你自己實(shí)現一個(gè)腳本引擎,而且沒(méi)有繼承父類(lèi)(如AbstractScriptEngine),那你就需要在你的腳本引擎里保存ScriptContext的實(shí)例,并且實(shí)現getContext()和setContext()方法。
Compilable和Invocable到現在為止,我們已經(jīng)達到了把BoolScript做為JSR223腳本引擎所需要的最低的要求。每當Java程序需要使用我們的腳本引擎的時(shí)候,它需要將BoolScript代碼作為字符串傳遞給我們的引擎。引擎內部使用一個(gè)解析器將這段字符串解析成抽象語(yǔ)法樹(shù)(abstract syntaxtree),然后將棵樹(shù)交給BoolTermEvaluator.evaluate()執行。以上的整個(gè)過(guò)程我們稱(chēng)為解釋執行(interpretation),與之相對的是編譯執行(compilation)。在這個(gè)過(guò)程中,BoolScript引擎被稱(chēng)為解釋器,與之相對的被稱(chēng)為編譯器。如果要作為編譯器使用的話(huà),BoolScript引擎要能做到將文本的BoolScript代碼轉換成中間形態(tài),這樣在執行這段代碼時(shí)就不用再解析成抽象語(yǔ)法樹(shù)。本章節講述了如何實(shí)現這個(gè)功能。
Java程序是編譯成稱(chēng)為Java字節碼的中間形態(tài)存放在.class文件中。運行時(shí)classloader載入.class文件,然后由JVM執行字節碼。BoolScript將使用Java字節碼作為中間形態(tài),這樣就可以不用自己定義中間形態(tài)和實(shí)現自己的虛擬機。
JSR 223定義的編譯概念的模型是javax.script.Compilable,因此BoolScriptEngine需要實(shí)現這個(gè)接口。下面這段BoolScriptHostApp.java里的代碼展示了如何使用可編譯的腳本引擎編譯和執行腳本代碼:
List<Boolean> boolAnswers = null;
//bsEngine is an instance of ScriptEngine
Compilable compiler = (Compilable) bsEngine;
CompiledScript compiledScript = compiler.compile("x & y\n\n");
Bindings bindings = new SimpleBindings();
bindings.put("x", new Boolean(true));
bindings.put("y", new Boolean(true));
boolAnswers = (List<Boolean>) compiledScript.eval(bindings);
printAnswers(boolAnswers);
Invocable invocable = (Invocable) bsEngine;
boolAnswers = (List<Boolean>) invocable.invoke("eval", new Boolean(true), new Boolean(false));
printAnswers(boolAnswers);
上面的代碼中,bsEngine是一個(gè)ScriptEngine的實(shí)例,它實(shí)現了Compilable接口。我們將它轉換成Compilable,然后調用compile()方法編譯代碼“x & y”。compile()內部將“x & y”轉換成下面的Java代碼:
package net.sf.model4lang.boolscript.generated;
import java.util.*;
import java.lang.reflect.*;
class TempBoolClass {
public static List<Boolean> eval(boolean x, boolean y)
{
List<Boolean> resultList = new ArrayList();
boolean result = false;
result = x & y;
resultList.add(new Boolean(result));
return resultList;
}
}
這個(gè)轉換將BoolScript代碼轉換成Java類(lèi)中的一個(gè)方法。類(lèi)名和方法名都是硬編碼的。BoolScript里的每個(gè)變量都將成為Java方法的一個(gè)參數。
將BoolScript代碼轉換成Java代碼只完成了一半的工作。之后我們還需要將Java代碼編譯成Java字節碼。在這我使用Java編譯API(JSR199,Java SE6.0的另一個(gè)新特性)直接在內存中編譯Java代碼。本文不討論Java編譯API,感興趣的讀者可以參考資源部分查找更多的信息。
Compilable接口規定了compile()方法必須返回一個(gè)CompiledScript的實(shí)例。CompiledScript在JSR223里是用來(lái)定義編譯結果的模型。不論我們怎么編譯腳本代碼,當編譯結束的時(shí)候,我們必須將編譯的結果封裝成CompiledScript的實(shí)例。在示例代碼中,我們定義了一個(gè)類(lèi)BoolCompiledScript繼承CompiledScript,將編譯后的BoolScript代碼存放到這個(gè)類(lèi)中。
當腳本代碼編譯后,Java程序可以調用CompiledScript實(shí)例的eval()方法反復地執行編譯后的代碼。在我們的例子中,我們調用CompiledScript的eval()方法的時(shí)候,需要將包含x和y變量的腳本上下文傳遞給這個(gè)方法,這在上面BoolScriptHostApp.java的代碼中已經(jīng)列出。
CompiledScript的eval()并不是唯一可以執行編譯后的腳本代碼的方法。如果腳本引擎實(shí)現了Invocable接口,我們就可以調用Invocable接口的invoke()方法執行腳本代碼。在我們的簡(jiǎn)單示例中,調用這兩個(gè)方法執行腳本代碼看上去并沒(méi)有什么區別。但是,實(shí)際使用中腳本引擎用戶(hù)通常用CompiledScript執行整段腳本,而使用Invocable執行獨立的函數(Java里稱(chēng)為方法)。如果你看一下Invocable的invoke()方法,你可以輕易地發(fā)現CompiledScript和Invocable的區別。和CompiledScript的eval()方法使用可選的腳本上下文作為參數不同的是,invoke()方法使用你想執行的函數名作為參數。
在上面引用的BoolScriptHostApp.java的代碼中,腳本引擎實(shí)例bsEngine實(shí)現了Invocable接口。我們將它轉成Invocable并調用它的invoke()方法。調用編譯后的腳本的函數和用反射調用Java類(lèi)的方法十分相似。你必須告訴invoke()方法你要調用的函數名,同時(shí)還要提供這個(gè)函數所需要的參數。我們已經(jīng)知道我們的函數名已經(jīng)硬編碼為eval了。因此我們將字符串“eval”作為invoke的第一個(gè)參數,同時(shí)我們還需要將eval的兩個(gè)Boolean參數傳遞給invoke()方法。
結論在這篇文章里,我討論了JSR 223的幾個(gè)主要特性:腳本引擎發(fā)現機制、Java綁定、Compilable和Invocable。JSR223中的Web腳本這里沒(méi)有涉及。如果了我們在BoolScript引擎里實(shí)現了Web腳本,那我們的腳本引擎使用者就可以在servlet容器里創(chuàng )建Web內容了。
即使不管和Java的整合,開(kāi)發(fā)一個(gè)語(yǔ)言的編譯器和解釋器也是一個(gè)龐大的事業(yè)。根據語(yǔ)言的復雜度,編譯器和解釋器的開(kāi)發(fā)可能會(huì )是一個(gè)非常麻煩的任務(wù)。感謝JSR 223,將我們的語(yǔ)言和Java整合從來(lái)沒(méi)有這么簡(jiǎn)單過(guò)。
關(guān)于作者Chaur Wu 是一個(gè)軟件開(kāi)發(fā)人員并出版過(guò)書(shū)。他曾合著(zhù)了有關(guān)設計模式和軟件建模的書(shū)籍。他同時(shí)也是開(kāi)源項目Model4Lang管理員,這個(gè)項目致力于為語(yǔ)言設計和構造提供基于模型的解決方案。
資源+本文中相關(guān)源代碼下載:
http://www.javaworld.com/javaworld/jw-04-2006/scripting/jw-0424-scripting.zip+BSF網(wǎng)站:http://jakarta.apache.org/bsf/
+一個(gè)JSR 223腳本引擎:http://model4lang.sourceforge.net
+JSR 223:http://www.jcp.org/en/jsr/detail?id=223
+Java SE 6.0源碼快照:http://download.java.net/jdk6
+Java SE 6.0 beta下載:http://java.sun.com/javase/6/download.jsp
+JSR 199:http://www.jcp.org/en/jsr/detail?id=199
+Matrix:
http://www.matrix.org.cn