2007 年 9 月 14 日 我們不 需要將動(dòng)態(tài)語(yǔ)言編譯為 Java? 字節(jié)碼就可以在 Java 應(yīng)用程序中使用它們。使用 Java Platform, Standard Edition 6 (Java SE)中添加的腳本包(并且向后兼容 Java SE 5),Java 代碼可以在運(yùn)行時(shí)以一種簡(jiǎn)單的、統(tǒng)一的方式調(diào)用多種動(dòng)態(tài)語(yǔ)言。本系列文章共分兩個(gè)部分,第 1 部分將介紹 Java 腳本 API 的各種特性。文章將使用一個(gè)簡(jiǎn)單的 Hello World 應(yīng)用程序展示 Java 代碼如何執(zhí)行腳本代碼以及腳本如何反過(guò)來(lái)執(zhí)行 Java 代碼。第 2 部分將深入研究 Java 腳本 API 的強(qiáng)大功能。 Java 開(kāi)發(fā)人員清楚 Java 并不是在任何情況下都是最佳的語(yǔ)言。今年,1.0 版本的 JRuby 和 Groovy 的發(fā)行引領(lǐng)了一場(chǎng)熱潮,促使人們紛紛在自己的 Java 應(yīng)用程序中添加動(dòng)態(tài)語(yǔ)言。Groovy、JRuby、Rhino、Jython 和一些其他的開(kāi)源項(xiàng)目使在所謂的腳本語(yǔ)言中編寫(xiě)代碼并在 JVM 中運(yùn)行成為了可能(請(qǐng)參閱 參 考資料)。通常,在 Java 代碼中集成這些語(yǔ)言需要對(duì)各種解釋器所特有的 API 和特性有所了解。 Java SE 6 中添加的 javax.script 包使集成動(dòng)態(tài)語(yǔ)言更加容易。通過(guò)使用一小組接口和具體類(lèi),這個(gè)包使我們能夠簡(jiǎn)單地調(diào)用多種腳本語(yǔ)言。但是,Java 腳本 API 的功能不只是在應(yīng)用程序中編寫(xiě)腳本;這個(gè)腳本包使我們能夠在運(yùn)行時(shí)讀取和調(diào)用外部腳本,這意味著我們可以動(dòng)態(tài)地修改這些腳本從而更改運(yùn)行應(yīng)用程序的行為。 Java 腳本 API | 腳本與動(dòng)態(tài)的對(duì)比 術(shù)語(yǔ)腳本 通常表示在解釋器 shell 中運(yùn)行的語(yǔ)言,它們往往沒(méi)有單獨(dú)的編譯步驟。術(shù)語(yǔ)動(dòng) 態(tài) 通常表示等到運(yùn)行時(shí)判斷變量類(lèi)型或?qū)ο笮袨榈恼Z(yǔ)言,往往具有閉包和連續(xù)特性。一些通用的編程語(yǔ)言同時(shí)具有這兩種特性。此處首選腳本語(yǔ)言 是因?yàn)楸疚牡闹攸c(diǎn)是 Java 腳本 API,而不是因?yàn)樘峒暗恼Z(yǔ)言缺少動(dòng)態(tài)特性。 | | 2006 年 10 月,Java 語(yǔ)言添加了腳本包,從而提供了一種統(tǒng)一的方式將腳本語(yǔ)言集成到 Java 應(yīng)用程序中去。對(duì)于語(yǔ)言開(kāi)發(fā)人員,他們可以使用這個(gè)包編寫(xiě)粘連代碼(glue code),從而使人們能夠在 Java 應(yīng)用程序中調(diào)用他們的語(yǔ)言。對(duì)于 Java 開(kāi)發(fā)人員,腳本包提供了一組類(lèi)和接口,允許使用一個(gè)公共 API 調(diào)用多種語(yǔ)言編寫(xiě)的腳本。因此,腳本包類(lèi)似于不同語(yǔ)言(比如說(shuō)不同的數(shù)據(jù)庫(kù))中的 Java Database Connectivity (JDBC) 包,可以使用一致的接口集成到 Java 平臺(tái)中去。 以前,在 Java 代碼中,動(dòng)態(tài)調(diào)用腳本語(yǔ)言涉及到使用各種語(yǔ)言發(fā)行版所提供的獨(dú)特類(lèi)或使用 Apache 的 Jakarta Bean Scripting Framework (BSF)。BSF 在一個(gè) API 內(nèi)部統(tǒng)一了一組腳本語(yǔ)言(請(qǐng)參閱 參 考資料)。使用 Java SE 6 腳本 API,二十余種腳本語(yǔ)言(AppleScript、Groovy、JavaScript、Jelly、PHP、Python、Ruby 和 Velocity)都可以集成到 Java 代碼中,這在很大程序上依賴(lài)的是 BSF。 腳本 API 在 Java 應(yīng)用程序和外部腳本之間提供了雙向可見(jiàn)性。Java 代碼不僅可以調(diào)用外部腳本,而且還允許那些腳本訪(fǎng)問(wèn)選定的 Java 對(duì)象。比如說(shuō),外部 Ruby 腳本可以對(duì) Java 對(duì)象調(diào)用方法,并訪(fǎng)問(wèn)對(duì)象的屬性,從而使腳本能夠?qū)⑿袨樘砑拥竭\(yùn)行中的應(yīng)用程序中(如果在開(kāi)發(fā)時(shí)無(wú)法預(yù)計(jì)應(yīng)用程序的行為)。 調(diào)用外部腳本可用于運(yùn)行時(shí)應(yīng)用程序增強(qiáng)、配置、監(jiān)控或一些其他的運(yùn)行時(shí)操作,比如說(shuō)在不停止應(yīng)用程序的情況下修改業(yè)務(wù)規(guī) 則。腳本包可能的作用包括: - 在比 Java 語(yǔ)言更簡(jiǎn)單的語(yǔ)言中編寫(xiě)業(yè)務(wù)規(guī)則,而不用借助成熟的規(guī)則引擎。
- 創(chuàng)建插件架構(gòu),使用戶(hù)能夠動(dòng)態(tài)地定制應(yīng)用程序。
- 將已有腳本集成到 Java 應(yīng)用程序中,比如說(shuō)處理或轉(zhuǎn)換文件文章的腳本。
- 使用成熟的編程語(yǔ)言(而不是屬性文件)從外部配置應(yīng)用程序的運(yùn)行時(shí)行為。
- 在 Java 應(yīng)用程序中添加一門(mén)特定于域的語(yǔ)言(domain-specific language)。
- 在開(kāi)發(fā) Java 應(yīng)用程序原型的過(guò)程中使用腳本語(yǔ)言。
- 在腳本語(yǔ)言中編寫(xiě)應(yīng)用程序測(cè)試代碼。
你好,腳本世界 HelloScriptingWorld 類(lèi)(本文中的相關(guān)代碼均可從 下 載部分 獲得)演示了 Java 腳本包的一些關(guān)鍵特性。它使用硬編碼的 JavaScript 作為示例腳本語(yǔ)言。此類(lèi)的 main() 方法(如清單 1 所示)將創(chuàng)建一個(gè) JavaScript 腳本引擎,然后分別調(diào)用五個(gè)方法(在下文的清單中有顯示)用于突出顯示腳本包的特性。 清單 1. HelloScriptingWorld main 方法 public static void main(String[] args) throws ScriptException, NoSuchMethodException { ScriptEngineManager scriptEngineMgr = new ScriptEngineManager(); ScriptEngine jsEngine = scriptEngineMgr.getEngineByName("JavaScript"); if (jsEngine == null) { System.err.println("No script engine found for JavaScript"); System.exit(1); } System.out.println("Calling invokeHelloScript..."); invokeHelloScript(jsEngine); System.out.println("\nCalling defineScriptFunction..."); defineScriptFunction(jsEngine); System.out.println("\nCalling invokeScriptFunctionFromEngine..."); invokeScriptFunctionFromEngine(jsEngine); System.out.println("\nCalling invokeScriptFunctionFromJava..."); invokeScriptFunctionFromJava(jsEngine); System.out.println("\nCalling invokeJavaFromScriptFunction..."); invokeJavaFromScriptFunction(jsEngine); } | main() 方法的主要功能是獲取一個(gè) javax.script.ScriptEngine 實(shí)例(清 單 1 中的前兩行代碼)。腳本引擎可以在特定的語(yǔ)言中加載并執(zhí)行腳本。它是 Java 腳本包中使用最為頻繁、作用最為重要的類(lèi)。我們從 javax.script.ScriptEngineManager 獲取一個(gè)腳本引擎(第一行代碼)。通常,程序只需要獲取一個(gè)腳本引擎實(shí)例,除非使用了很多種腳本語(yǔ)言。 ScriptEngineManager 類(lèi) ScriptEngineManager 可能是腳本包中惟一一個(gè)經(jīng)常使用的具體類(lèi);其他大多數(shù)都是接口。它或許是腳本包中惟一的一個(gè)要直接或間接地(通過(guò) Spring Framework 之類(lèi)的依賴(lài)性注入機(jī)制)實(shí)例化的類(lèi)。ScriptEngineManager 可以使用以下三種方式返回腳本引擎: - 通過(guò)引擎或語(yǔ)言的名稱(chēng),比如說(shuō) 清 單 1 請(qǐng)求
JavaScript 引擎。 - 通過(guò)該語(yǔ)言腳本共同使用的文件擴(kuò)展名,比如說(shuō) Ruby 腳本的 .rb。
- 通過(guò)腳本引擎聲明的、知道如何處理的 MIME 類(lèi)型。
| 本文示例為什么要使用 JavaScript? 本文中的 Hello World 示例使用了部分 JavaScript 腳本,這是因?yàn)?JavaScript 代碼易于理解,不過(guò)主要還是因?yàn)?Sun Microsystems 和 BEA Systems 所提供的 Java 6 運(yùn)行時(shí)環(huán)境附帶有基于 Mozilla Rhino 開(kāi)源 JavaScript 實(shí)現(xiàn)的 JavaScript 解釋器。使用 JavaScript,我們無(wú)需在類(lèi)路徑中添加腳本語(yǔ)言 JAR 文件。 | | ScriptEngineManager 間接查找和創(chuàng)建腳本引擎。也就是說(shuō),當(dāng)實(shí)例化腳本引擎管理程序時(shí),ScriptEngineManager 會(huì)使用 Java 6 中新增的服務(wù)發(fā)現(xiàn)機(jī)制在類(lèi)路徑中查找所有注冊(cè)的 javax.script.ScriptEngineFactory 實(shí)現(xiàn)。這些工廠類(lèi)封裝在 Java 腳本 API 實(shí)現(xiàn)中;也許您永遠(yuǎn)都不需要直接處理這些工廠類(lèi)。 ScriptEngineManager 找到所有的腳本引擎工廠類(lèi)之后,它會(huì)查詢(xún)各個(gè)類(lèi)并判斷是否能夠創(chuàng)建所請(qǐng)求類(lèi)型的腳本引擎 —— 清 單 1 中為 JavaScript 引擎。如果工廠說(shuō)可以創(chuàng)建所需語(yǔ)言的腳本引擎,那么管理程序?qū)⒁蠊S創(chuàng)建一個(gè)引擎并將其返回給調(diào)用者。如果沒(méi)有找到所請(qǐng)求語(yǔ)言的工廠,那么管理程序?qū)⒎?回 null ,清 單 1 中的代碼將檢查 null 返回值并做出預(yù)防。 ScriptEngine 接口 如前所述,代碼將使用 ScriptEngine 實(shí)例執(zhí)行腳本。腳本引擎充當(dāng)腳本代碼和最后執(zhí)行代碼的底層語(yǔ)言解釋器或編譯器之間的中間程序。這樣,我們就不需要了解各個(gè)解釋器使用哪些類(lèi)來(lái)執(zhí)行腳本。比 如說(shuō),JRuby 腳本引擎可以將代碼傳遞給 JRuby 的 org.jruby.Ruby 類(lèi)的一個(gè)實(shí)例,首先將腳本編譯成中間形式,然后再調(diào)用它計(jì)算腳本并處理返回值。腳本引擎實(shí)現(xiàn)隱藏了一些細(xì)節(jié),包括解釋器如何與 Java 代碼共享類(lèi)定義、應(yīng)用程序?qū)ο蠛洼斎?輸出流。 圖 1 顯示了應(yīng)用程序、Java 腳本 API 和 ScriptEngine 實(shí)現(xiàn)、腳本語(yǔ)言解釋器之間的總體關(guān)系。我們可以看到,應(yīng)用程序只依賴(lài)于腳本 API,它提供了 ScriptEngineManager 類(lèi)和 ScriptEngine 接口。ScriptEngine 實(shí)現(xiàn)組件處理使用特定腳本語(yǔ)言解釋器的細(xì)節(jié)。 圖 1:腳本 API 組件關(guān)系 您可能會(huì)問(wèn):如何才能獲取腳本引擎實(shí)現(xiàn)和語(yǔ)言解釋器所需的 JAR 文件呢?最好的方法是在 java.net 上托管的開(kāi)源 Scripting 項(xiàng)目中查找腳本引擎實(shí)現(xiàn)(請(qǐng)參閱 參 考資料)。您可以在 java.net 上找到許多語(yǔ)言的腳本引擎實(shí)現(xiàn)和其他網(wǎng)站的鏈接。Scripting 項(xiàng)目還提供了各種鏈接,通過(guò)這些鏈接可以下載受支持的腳本語(yǔ)言的解釋器。 在 清 單 1 中,main() 方法將 ScriptEngine 傳遞給各個(gè)方法用于計(jì)算該方法的 JavaScript 代碼。第一個(gè)方法如清單 2 所示。invokeHelloScript() 方法調(diào)用腳本引擎的 eval 方法計(jì)算和執(zhí)行 JavaScript 代碼中的特定字符串。ScriptEngine 接口定義了 6 個(gè)重載的 eval() 方法,用于將接收的腳本當(dāng)作字符串或 java.io.Reader 對(duì)象計(jì)算,java.io.Reader 對(duì)象一般用于從外部源(例如文件)讀取腳本。 清單 2. invokeHelloScript 方法 private static void invokeHelloScript(ScriptEngine jsEngine) throws ScriptException { jsEngine.eval("println('Hello from JavaScript')"); } | | 腳本執(zhí)行上下文 HelloScriptingWorld 應(yīng)用程序中的示例腳本 使用 JavaScript println() 函數(shù)向控制臺(tái)輸出結(jié)果,但是我們擁有輸入和輸出流的完全控制權(quán)。腳本引擎提供了一個(gè)選項(xiàng)用于修改腳本執(zhí)行的上下文,這意味著我們可以修改標(biāo) 準(zhǔn)輸入流、標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤流,同時(shí)還可以定義哪些全局變量和 Java 對(duì)象對(duì)正在執(zhí)行的腳本可用。 | | invokeHelloScript() 方法中的 JavaScript 將 Hello from JavaScript 輸出到標(biāo)準(zhǔn)輸出流,在本例中為控制臺(tái)窗口。(清 單 6 含有運(yùn)行 HelloScriptingWorldApplication 時(shí)的完整輸出。) 注意,類(lèi)中的這一方法和其他方法都聲明拋出了 javax.script.ScriptException 。 這個(gè)選中的異常(腳本包中定義的惟一一個(gè)異常)表示引擎無(wú)法解析或執(zhí)行給定的代碼。所有腳本引擎 eval() 方法都聲明拋出一個(gè) ScriptException ,因此我們的代碼需要適當(dāng)處理這些異常。 清單 3 顯示了兩個(gè)有關(guān)的方法:defineScriptFunction() 和 invokeScriptFunctionFromEngine() 。defineScriptFunction() 方法還使用一段硬編碼的 JavaScript 代碼調(diào)用腳本引擎的 eval() 方法。但是有一點(diǎn)需要注意,該方法的所有工作只是定義了一個(gè) JavaScript 函數(shù) sayHello() 。并沒(méi)有執(zhí)行 任何代碼。sayHello() 函數(shù)只有一個(gè)參數(shù),它會(huì)使用 println() 語(yǔ)句將這個(gè)參數(shù)輸出到控制臺(tái)。腳本引擎的 JavaScript 解釋器將這個(gè)函數(shù)添加到全局環(huán)境,以供后續(xù)的 eval 調(diào)用使用(該調(diào)用發(fā)生在 invokeScriptFunctionFromEngine() 方法中,這并不奇怪)。 清單 3. defineScriptFunction 和 invokeScriptFunctionFromEngine 方法 private static void defineScriptFunction(ScriptEngine engine) throws ScriptException { // Define a function in the script engine engine.eval( "function sayHello(name) {" + " println('Hello, ' + name)" + "}" ); } private static void invokeScriptFunctionFromEngine(ScriptEngine engine) throws ScriptException { engine.eval("sayHello('World!')"); } | 這兩個(gè)方法演示了腳本引擎可以維持應(yīng)用程序組件的狀態(tài),并且能夠在后續(xù)的 eval() 方法調(diào)用過(guò)程中使用其狀態(tài)。invokeScriptFunctionFromEngine() 方法可以利用所維持的狀態(tài),方法是調(diào)用定義在 eval() 調(diào)用中的 sayHello() JavaScript 函數(shù)。 許多腳本引擎在 eval() 調(diào)用之間維持全局變量和函數(shù)的狀態(tài)。但是有一點(diǎn)值得格外注意,Java 腳本 API 并不要求腳本引擎提供這一特性。本文中所使用的 JavaScript、Groovy 和 JRuby 腳本引擎確實(shí)在 eval() 調(diào)用之間維持了這些狀態(tài)。 清單 4 中的代碼在前一個(gè)示例的基礎(chǔ)上做了幾分修改。原來(lái)的 invokeScriptFunctionFromJava() 方法在調(diào)用 sayHello() JavaScript 函數(shù)時(shí)沒(méi)有使用 ScriptEngine 的 eval() 方法或 JavaScript 代碼。與此不同,清單 4 中的方法使用 Java 腳本 API 的 javax.script.Invocable 接口調(diào)用由腳本引擎所維持的函數(shù)。invokeScriptFunctionFromJava() 方法將腳本引擎對(duì)象傳遞給 Invocable 接口,然后對(duì)該接口調(diào)用 invokeFunction() 方法,最終使用給定的參數(shù)調(diào)用 sayHello() JavaScript 函數(shù)。如果調(diào)用的函數(shù)需要返回值,則 invokeFunction() 方法會(huì)將值封裝為 Java 對(duì)象 類(lèi)型并返回。 清單 4. invokeScriptFunctionFromJava 方法 private static void invokeScriptFunctionFromJava(ScriptEngine engine) throws ScriptException, NoSuchMethodException { Invocable invocableEngine = (Invocable) engine; invocableEngine.invokeFunction("sayHello", "from Java"); } | | 使用代理實(shí)現(xiàn)高級(jí)腳本調(diào)用 當(dāng)腳本函數(shù)或方法實(shí)現(xiàn)了一個(gè) Java 接口時(shí),就可以使用高級(jí) Invocable 。Invocable 接口定義了一個(gè) getInterface() 方法,該方法使用接口做為參數(shù)并且將返回一個(gè)實(shí)現(xiàn)該接口的 Java 代碼對(duì)象。從腳本引擎獲得代理對(duì)象之后,可以將它作為正常的 Java 對(duì)象對(duì)待。對(duì)該代理調(diào)用的方法將委托給腳本引擎通過(guò)腳本語(yǔ)言執(zhí)行。 | | 注意,清 單 4 中沒(méi)有 JavaScript 代碼。Invocable 接口允許 Java 代碼調(diào)用腳本函數(shù),而無(wú)需知道其實(shí)現(xiàn)語(yǔ)言。如果腳本引擎無(wú)法找到給定名稱(chēng)或參數(shù)類(lèi)型的函數(shù),那么 invokeFunction() 方法將拋出一個(gè) java.lang.NoSuchMethodException 。 Java 腳本 API 并不要求腳本引擎實(shí)現(xiàn) Invocable 接口。實(shí)際上,清 單 4 中的代碼應(yīng)該使用 instanceof 運(yùn)算符確保腳本引擎在轉(zhuǎn)換(cast)之前實(shí)現(xiàn)了 Invocable 接口。 通過(guò)腳本代碼調(diào)用 Java 方法 清 單 3 和 清 單 4 中的示例展示了 Java 代碼如何調(diào)用腳本語(yǔ)言中定義的函數(shù)或方法。您可能會(huì)問(wèn):腳本語(yǔ)言中編寫(xiě)的代碼是否可以反過(guò)來(lái)對(duì) Java 對(duì)象調(diào)用方法呢?答案是可以。清單 5 中的 invokeJavaFromScriptFunction() 方法顯示了如何使腳本引擎能夠訪(fǎng)問(wèn) Java 對(duì)象,以及腳本代碼如何才能對(duì)這些 Java 對(duì)象調(diào)用方法。明確的說(shuō),invokeJavaFromScriptFunction() 方法使用腳本引擎的 put() 方法將 HelloScriptingWorld 類(lèi)的實(shí)例本身提供給引擎。當(dāng)引擎擁有 Java 對(duì)象的訪(fǎng)問(wèn)權(quán)之后(使用 put() 調(diào)用所提供的名稱(chēng)),eval() 方法腳本中的腳本代碼將使用該對(duì)象。 清單 5. invokeJavaFromScriptFunction 和 getHelloReply 方法 private static void invokeJavaFromScriptFunction(ScriptEngine engine) throws ScriptException { engine.put("helloScriptingWorld", new HelloScriptingWorld()); engine.eval( "println('Invoking getHelloReply method from JavaScript...');" + "var msg = helloScriptingWorld.getHelloReply(vJavaScript');" + "println('Java returned: ' + msg)" ); } /** Method invoked from the above script to return a string. */ public String getHelloReply(String name) { return "Java method getHelloReply says, 'Hello, " + name + "'"; } | 清 單 5 中的 eval() 方法調(diào)用中所包含的 JavaScript 代碼使用腳本引擎的 put() 方法調(diào)用所提供的變量名稱(chēng) helloScriptingWorld 訪(fǎng)問(wèn)并使用 HelloScriptingWorld Java 對(duì)象。清 單 5 中的第二行 JavaScript 代碼將調(diào)用 getHelloReply() 公有 Java 方法。getHelloReply() 方法將返回 Java method getHelloReply says, 'Hello, <parameter>' 字符串。eval() 方法中的 JavaScript 代碼將 Java 返回值賦給 msg 變量,然后再將其打印輸出給控制臺(tái)。 | Java 對(duì)象轉(zhuǎn)換 當(dāng)腳本引擎使運(yùn)行于引擎環(huán)境中的腳本能夠使用 Java 對(duì)象時(shí),引擎需要將其封裝到適用于該腳本語(yǔ)言的對(duì)象類(lèi)型中。封裝可能會(huì)涉及到一些適當(dāng)?shù)膶?duì)象-值轉(zhuǎn)換,比如說(shuō)允許 Java Integer 對(duì)象直接在腳本語(yǔ)言的數(shù)學(xué)表達(dá)式中使用。關(guān)于如何將 Java 對(duì)象轉(zhuǎn)換為腳本對(duì)象的研究是與各個(gè)腳本語(yǔ)言的引擎特別相關(guān)的,并且不在本文的討論范圍之內(nèi)。但是,您應(yīng)該意識(shí)到轉(zhuǎn)換的發(fā)生,因?yàn)榭梢酝ㄟ^(guò)測(cè)試來(lái)確保所使用 的腳本語(yǔ)言執(zhí)行轉(zhuǎn)換的方式符合您的期望。 | | ScriptEngine.put 及其相關(guān) get() 方法是在運(yùn)行于腳本引擎中的 Java 代碼和腳本之間共享對(duì)象和數(shù)據(jù)的主要途徑。(有關(guān)這一方面的詳細(xì)論述,請(qǐng)參閱本文后面的 Script-execution scope 一節(jié)。)當(dāng)我們調(diào)用引擎的 put() 方法時(shí),腳本引擎會(huì)將第二個(gè)參數(shù)(任何 Java 對(duì)象)關(guān)聯(lián)到特定的字符串關(guān)鍵字。大多數(shù)腳本引擎都是讓腳本使用特定的變量名稱(chēng)來(lái)訪(fǎng)問(wèn) Java 對(duì)象。腳本引擎可以隨意對(duì)待傳遞給 put() 方法的名稱(chēng)。比如說(shuō),JRuby 腳本引擎讓 Ruby 代碼使用全局 $helloScriptingWorld 對(duì)象訪(fǎng)問(wèn) helloScriptingWorld ,以符合 Ruby 全局變量的語(yǔ)法。 腳本引擎的 get() 方法檢索腳本環(huán)境中可用的值。一般而言,Java 代碼通過(guò) get() 方法可以訪(fǎng)問(wèn)腳本環(huán)境中的所有全局變量和函數(shù)。但是只有明確使用 put() 與腳本共享的 Java 對(duì)象才可以被腳本訪(fǎng)問(wèn)。 外部腳本在運(yùn)行著的應(yīng)用程序中訪(fǎng)問(wèn)和操作 Java 對(duì)象的這種功能是擴(kuò)展 Java 程序功能的一項(xiàng)強(qiáng)有力的技巧。(第 2 部分將通過(guò)示例研究這一技巧)。
運(yùn)行 HelloScriptingWorld 應(yīng)用程序 您可以通過(guò)下載和構(gòu)建源代碼來(lái)運(yùn)行 HelloScriptingWorld 應(yīng)用程序。此 .zip 中文件含有一個(gè) Ant 腳本和一個(gè) Maven 構(gòu)建腳本,可以幫助大家編譯和運(yùn)行示例應(yīng)用程序。請(qǐng)執(zhí)行以下步驟: - 下 載 此 .zip 文件。
- 創(chuàng)建一個(gè)新目錄,比如說(shuō) java-scripting,并將步驟 1 中所下載的文件解壓到該目錄中。
- 打開(kāi)命令行 shell 并轉(zhuǎn)到該目錄。
- 運(yùn)行
ant run-hello 命令。 您應(yīng)該可以看到類(lèi)似于清單 6 的 Ant 控制臺(tái)輸出。注意,defineScriptFunction() 函數(shù)沒(méi)有產(chǎn)生任何輸出,因?yàn)樗m然定義了輸出但是卻沒(méi)有調(diào)用 JavaScript 函數(shù)。 清單 6. 運(yùn)行 HelloScriptingWorld 時(shí)的輸出 Calling invokeHelloScript... Hello from JavaScript Calling defineScriptFunction... Calling invokeScriptFunctionFromEngine... Hello, World! Calling invokeScriptFunctionFromJava... Hello, from Java Calling invokeJavaFromScriptFunction... Invoking getHelloReply method from JavaScript... Java returned: Java method getHelloReply says, 'Hello, JavaScript' |
Java 5 兼容性 Java SE 6 引入了 Java 腳本 API,但是您也可以使用 Java SE 5 運(yùn)行此 API。只需要提供缺少的 javax.script 包類(lèi)的一個(gè)實(shí)現(xiàn)即可。所幸的是,Java Specification Request 223 參考實(shí)現(xiàn)中含有這個(gè)實(shí)現(xiàn)(請(qǐng)參閱 參 考資料 獲得下載鏈接。)JSR 223 對(duì) Java 腳本 API 做出了定義。 如果您已經(jīng)下載了 JSR 223 參考實(shí)現(xiàn),解壓下載文件并將 script-api.jar、script-js.jar 和 js.jar 文件復(fù)制到您的類(lèi)路徑下。這些文件將提供腳本 API、JavaScript 腳本引擎接口和 Java SE 6 中所附帶的 JavaScript 腳本引擎。
腳本執(zhí)行作用域 與簡(jiǎn)單地調(diào)用引擎的 get() 和 put() 方法相比,如何將 Java 對(duì)象公開(kāi)給運(yùn)行于腳本引擎中的腳本具有更好的可配置性。當(dāng)我們?cè)谀_本引擎上調(diào)用 get() 或 put() 方法時(shí),引擎將會(huì)在 javax.script.Bindings 接口的默認(rèn)實(shí)例中檢索或保存所請(qǐng)求的關(guān)鍵字。(Bindings 接口只是一個(gè) Map 接口,用于強(qiáng)制關(guān)鍵字為字符串。) 當(dāng)代碼調(diào)用腳本引擎的 eval() 方法時(shí),將使用引擎默認(rèn)綁定的關(guān)鍵字和值。但是,您可以為 eval() 調(diào)用提供自己的 Bindings 對(duì)象,以限制哪些變量和對(duì)象對(duì)于該特定腳本可見(jiàn)。該調(diào)用外表上類(lèi)似于 eval(String, Bindings) 或 eval(Reader, Bindings) 。要幫助您創(chuàng)建自定義的 Bindings ,腳本引擎將提供一個(gè) createBindings() 方法,該方法和返回值是一個(gè)內(nèi)容為空的 Bindings 對(duì)象。使用 Bindings 對(duì)象臨時(shí)調(diào)用 eval 將隱藏先前保存在引擎默認(rèn)綁定中的 Java 對(duì)象。 要添加功能,腳本引擎含有兩個(gè)默認(rèn)綁定:其一為 get() 和 put() 調(diào)用所使用的 “引擎作用域” 綁定 ;其二為 “全局作用域” 綁定,當(dāng)無(wú)法在 “引擎作用域” 中找到對(duì)象時(shí),引擎將使用第二種綁定進(jìn)行查找。腳本引擎并不需要使腳本能夠訪(fǎng)問(wèn)全局綁定。大多數(shù)腳本都可以訪(fǎng)問(wèn)它。 “全局作用域” 綁定的設(shè)計(jì)目的是在不同的腳本引擎之間共享對(duì)象。ScriptEngineManager 實(shí)例返回的所有腳本引擎都是 “全局作用域” 綁定對(duì)象。您可以使用 getBindings(ScriptContext.GLOBAL_SCOPE) 方法檢索某個(gè)引擎的全局綁定,并且可以使用 setBindings(Bindings, ScriptContext.GLOBAL_SCOPE) 方法為引擎設(shè)置全局綁定。 ScriptContext 是一個(gè)定義和控制腳本引擎運(yùn)行時(shí)上下文的接口。腳本引擎的 ScriptContext 含有 “引擎” 和 “全局” 作用域綁定,以及用于標(biāo)準(zhǔn)輸入和輸出操作的輸入和輸出流。您可以使用引擎的 getContext() 方法獲取并操作腳本引擎的上下文。 一些腳本 API 概念,比如說(shuō)作用域、綁定 和上下文,開(kāi)始看來(lái)會(huì)令人迷 惑,因?yàn)樗鼈兊暮x有交叉的地方。本文的源代碼下載文件含有一個(gè)名為 ScriptApiRhinoTest 的 JUnit 測(cè)試文件,位于 src/test/java directory 目錄,該文件可以通過(guò) Java 代碼幫助解釋這些概念。 未來(lái)的計(jì)劃 現(xiàn)在,大家已經(jīng)對(duì) Java 腳本 API 有了最基本的認(rèn)識(shí),本系列文章的第 2 部分將在此基礎(chǔ)上進(jìn)行擴(kuò)展,為大家演示一個(gè)更為實(shí)際的示例應(yīng)用程序。該應(yīng)用程序?qū)⑹褂?Groovy、Ruby 和 JavaScript 一起編寫(xiě)的外部腳本文件來(lái)定義可在運(yùn)行時(shí)修改的業(yè)務(wù)邏輯。如您如見(jiàn),在腳本語(yǔ)言中定義業(yè)務(wù)規(guī)則可以使規(guī)則的編寫(xiě)更加輕松,并且更易于程序員之外的人員閱 讀,比如說(shuō)業(yè)務(wù)分析師或規(guī)則編寫(xiě)人員。
下載 描述 | 名字 | 大小 | 下載方法 | 源代碼和 JAR 文件 | j-javascripting1.zip | 116KB | HTTP |
參考資料 學(xué)習(xí) 獲得產(chǎn)品和技術(shù) - Groovy: 下載最新的 Groovy 發(fā)行版。
- Java SE 6 和 BEA JRockit:內(nèi)部支持 Java 腳本 API 的開(kāi)發(fā)包和運(yùn)行時(shí)環(huán)境,其中含有一個(gè)簡(jiǎn)化版本的 Mozilla Rhino JavaScript 引擎。
- Scripting 項(xiàng)目:java.net 上的開(kāi)源 Scripting 項(xiàng)目提供了二十余種語(yǔ)言的腳本引擎接口,和其他一些知名 Java 腳本引擎的鏈接。要使用其中的腳本語(yǔ)言,需要安裝此項(xiàng)目中的腳本引擎實(shí)現(xiàn) JAR 文件,和腳本語(yǔ)言解釋器 JAR 文件。
- Scripting for the Java Platform 1.0 參考實(shí)現(xiàn):JSR-223 參考實(shí)現(xiàn)提供了三個(gè) JAR 文件,允許 Java 腳本 API 在 Java SE 5 中運(yùn)行。下載并解壓 sjp-1_0-fr-ri.zip 文件,然后將 js.jar、script-api.jar 和 script-js.jar 文件放置在類(lèi)路徑下。
討論
關(guān)于作者 | | | Tom McQueeney 是 Idea Integration(美國(guó)的一家咨詢(xún)公司)的一名 Java 開(kāi)發(fā)人員和應(yīng)用程序架構(gòu)師。他熱衷于將一些動(dòng)態(tài)語(yǔ)言(如 Ruby 和 Groovy)集成到 Java 項(xiàng)目中,從而提高開(kāi)發(fā)的速度、效率和樂(lè)趣。他曾經(jīng)是 O'Reilly OSCON 和 ApacheCon EuropeHe 的發(fā)言人,并且擔(dān)任過(guò) Denver Java Users Group 的總裁一職。他和他的妻子(也是一名認(rèn)證的 Java 架構(gòu)師)居住在華盛頓。 | |