如今,許多 Java 開發人員都喜歡在 Java 平臺中使用腳本語言,可是使用編譯到 Java 字節碼中的動態語言有時是不可行的。在某些狀況中,直接編寫一個 Java 應用程序的腳本 部分 或者在一個腳本中調用特定的 Java 對象是更快捷、更高效的方法。shell
這就是 javax.script
產生的緣由了。Java Scripting API 是從 Java 6 開始引入的,它填補了便捷的小腳本語言和健壯的 Java 生態系統之間的鴻溝。經過使用 Java Scripting API,您就能夠在您的 Java 代碼中快速整合幾乎全部的腳本語言,這使您可以在解決一些很小的問題時有更多可選擇的方法。數據庫
1. 使用 jrunscript 執行 JavaScript編程
每個新的 Java 平臺發佈都會帶來新的命令行工具集,它們位於 JDK 的 bin 目錄。Java 6 也同樣,其中 jrunscript
即是 Java 平臺工具集中的一個不小的補充。數組
設想一個編寫命令行腳本進行性能監控的簡單問題。這個工具將借用 jmap
(見本系列文章 前一篇文章 中的介紹),每 5 秒鐘運行一個 Java 進程,從而瞭解進程的運行情況。通常狀況下,咱們會使用命令行 shell 腳原本完成這樣的工做,可是這裏的服務器應用程序部署在一些差異很大的平臺上,包括 Windows® 和 Linux®。系統管理員將會發現編寫可以同時運行在兩個平臺的 shell 腳本是很痛苦的。一般的作法是編寫一個 Windows 批處理文件和一個 UNIX® shell 腳本,同時保證這兩個文件同步更新。瀏覽器
可是,任何閱讀過 The Pragmatic Programmer 的人都知道,這嚴重違反了 DRY (Don't Repeat Yourself) 原則,並且會產生許多缺陷和問題。咱們真正但願的是編寫一種與操做系統無關的腳本,它可以在全部的平臺上運行。
固然,Java 語言是平臺無關的,可是這裏並非須要使用 「系統」 語言的狀況。咱們須要的是一種腳本語言 — 如,JavaScript。
清單 1 顯示的是咱們所須要的簡單 shell 腳本:
清單 1. periodic.js
while (true) { echo("Hello, world!"); } |
因爲常常與 Web 瀏覽器打交道,許多 Java 開發人員已經知道了 JavaScript(或 ECMAScript;JavaScript 是由 Netscape 開發的一種 ECMAScript 語言)。問題是,系統管理員要如何運行這個腳本?
固然,解決方法是 JDK 所帶的 jrunscript
實用程序,如清單 2 所示:
清單 2. jrunscript
C:\developerWorks\5things-scripting\code\j***c>jrunscript periodic.js Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! ... |
注意,您也可使用 for
循環按照指定的次數來循環執行這個腳本,而後才退出。基本上,jrunscript
可以讓您執行 JavaScript 的全部操做。唯一不一樣的是它的運行環境不是瀏覽器,因此運行中不會有 DOM。所以,最頂層的函數和對象稍微有些不一樣。
由於 Java 6 將 Rhino ECMAScript 引擎做爲 JDK 的一部分,jrunscript
能夠執行任何傳遞給它的 ECMAScript 代碼,不論是一個文件(如此處所示)或是在更加交互式的 REPL(「Read-Evaluate-Print-Loop」)shell 環境。運行 jrunscript
就能夠訪問 REPL shell。
可以編寫 JavaScript/ECMAScript 代碼是很是好的,可是咱們不但願被迫從新編譯咱們在 Java 語言中使用的全部代碼 — 這是違背咱們初衷的。幸虧,全部使用 Java Scripting API 引擎的代碼都徹底可以訪問整個 Java 生態系統,由於本質上一切代碼都仍是 Java 字節碼。因此,回到咱們以前的問題,咱們能夠在 Java 平臺上使用傳統的 Runtime.exec()
調用來啓動進程,如清單 3 所示:
清單 3. Runtime.exec() 啓動 jmap
var p = java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ]) p.waitFor() |
數組 arguments
是指向傳遞到這個函數參數的 ECMAScript 標準內置引用。在最頂層的腳本環境中,則是傳遞給腳本自己的的參數數組(命令行參數)。因此,在清單 3 中,這個腳本預期接收一個參數,該參數包含要映射的 Java 進程的 VMID。
除此以外,咱們能夠利用自己爲一個 Java 類的 jmap
,而後直接調用它的 main()
方法,如清單 4 所示。有了這個方法,咱們不須要 「傳輸」 Process
對象的 in/out/err
流。
清單 4. JMap.main()
var args = [ "-histo", arguments[0] ] Packages.sun.tools.jmap.JMap.main(args) |
Packages
語法是一個 Rhino ECMAScript 標識,它指向已經 Rhino 內建立的位於核心 java.*
包以外的 Java 包。
從腳本調用 Java 對象僅僅完成了一半的工做:Java 腳本環境也提供了從 Java 代碼調用腳本的功能。這隻須要實例化一個ScriptEngine
對象,而後加載和評估腳本,如清單 5 所示:
清單 5. Java 平臺的腳本調用
import java.io.*; import javax.script.*; public class App { public static void main(String[] args) { try { ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript"); for (String arg : args) { FileReader fr = new FileReader(arg); engine.eval(fr); } } catch(IOException ioEx) { ioEx.printStackTrace(); } catch(ScriptException scrEx) { scrEx.printStackTrace(); } } } |
eval()
方法也能夠直接操做一個 String
,因此這個腳本不必定必須是文件系統的一個文件 — 它能夠來自於數據庫、用戶輸入,或者甚至能夠基於環境和用戶操做在應用程序中生成。
僅僅調用一個腳本還不夠:腳本一般會與 Java 環境中建立的對象進行交互。這時,Java 主機環境必須建立一些對象並將它們綁定,這樣腳本就能夠很容易找到和使用這些對象。這個過程是 ScriptContext
對象的任務,如清單 6 所示:
清單 6. 爲腳本綁定對象
import java.io.*; import javax.script.*; public class App { public static void main(String[] args) { try { ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript"); for (String arg : args) { Bindings bindings = new SimpleBindings(); bindings.put("author", new Person("Ted", "Neward", 39)); bindings.put("title", "5 Things You Didn't Know"); FileReader fr = new FileReader(arg); engine.eval(fr, bindings); } } catch(IOException ioEx) { ioEx.printStackTrace(); } catch(ScriptException scrEx) { scrEx.printStackTrace(); } } } |
訪問所綁定的對象很簡單 — 所綁定對象的名稱是做爲全局命名空間引入到腳本的,因此在 Rhino 中使用 Person
很簡單,如清單 7 所示:
清單 7. 是誰撰寫了本文?
println("Hello from inside scripting!") println("author.firstName = " + author.firstName) |
您能夠看到,JavaBeans 樣式的屬性被簡化爲使用名稱直接訪問,這就好像它們是字段同樣。
腳本語言的缺點一直存在於性能方面。其中的緣由是,大多數狀況下腳本語言是 「即時」 解譯的,於是它在執行時會損失一些解析和驗證文本的時間和 CPU 週期。運行在 JVM 的許多腳本語言最終會將接收的代碼轉換爲 Java 字節碼,至少在腳本被第一次解析和驗證時進行轉換;在 Java 程序關閉時,這些即時編譯的代碼會消失。將頻繁使用的腳本保持爲字節碼形式能夠幫助提高可觀的性能。
咱們能夠以一種很天然和有意義的方法使用 Java Scripting API。若是返回的 ScriptEngine
實現了 Compilable
接口,那麼這個接口所編譯的方法可用於將腳本(以一個 String
或一個 Reader
傳遞過來的)編譯爲一個 CompiledScript
實例,而後它可用於在 eval()
方法中使用不一樣的綁定重複地處理編譯後的代碼,如清單 8 所示:
清單 8. 編譯解譯後的代碼
import java.io.*; import javax.script.*; public class App { public static void main(String[] args) { try { ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript"); for (String arg : args) { Bindings bindings = new SimpleBindings(); bindings.put("author", new Person("Ted", "Neward", 39)); bindings.put("title", "5 Things You Didn't Know"); FileReader fr = new FileReader(arg); if (engine instanceof Compilable) { System.out.println("Compiling...."); Compilable compEngine = (Compilable)engine; CompiledScript cs = compEngine.compile(fr); cs.eval(bindings); } else engine.eval(fr, bindings); } } catch(IOException ioEx) { ioEx.printStackTrace(); } catch(ScriptException scrEx) { scrEx.printStackTrace(); } } } |
在大多數狀況中,CompiledScript
實例須要存儲在一個長時間存儲中(例如,servlet-context
),這樣才能避免一次次地重複編譯相同的腳本。然而,若是腳本發生變化,您就須要建立一個新的 CompiledScript
來反映這個變化;一旦編譯完成,CompiledScript
就再也不執行原始的腳本文件內容。
Java Scripting API 在擴展 Java 程序的範圍和功能方面前進了很大一步,而且它將腳本語言的編碼效率的優點帶到 Java 環境。jrunscript
— 它顯然不是很難編寫的程序 — 以及 javax.script
給 Java 開發人員帶來了諸如 Ruby (JRuby) 和 ECMAScript (Rhino) 等腳本語言的優點,同時還不會破壞 Java 環境的生態系統和可擴展性。
請繼續閱讀下一篇文章 5 件事 系列文章:JDBC。
學習
- 您不知道的 5 件事…… :瞭解 Java 平臺中您還有多少不知道的知識,這個系列專門將繁瑣的 Java 技術變成很是有用的編程技巧。
- 「 動態調用動態語言,第 1 部分:引入 Java 腳本 API」 (Tom McQueeney,developerWorks,2007 年 9 月):文章包含兩個部分,第 1 部分介紹了 Java 腳本 API 的特性;第 2 部分則更深刻地分析它的許多強大的應用。
- 「 JavaScript EE,第 3 部分:結合使用 Java Scripting API 和 JSP」 (Andrei Cioroianu,developerWorks,2009 年 6 月):詳細瞭解如何結合使用 JavaScript 和 Java 平臺,以及如何建立 Web 瀏覽器禁用了 JavaScript 時仍然能運行的 Ajax 用戶界面。
- JDK Tools and Utilities:瞭解在 「性能監控應關注的 5 件事」 中所討論的實驗性監控和故障診斷工具,包括
jmap
。
- developerWorks Java 技術專區:這裏有數百篇關於 Java 編程各個方面的文章。
討論
- 加入 My developerWorks 中文社區。查看開發人員推進的博客、論壇、組和 wikis,並與其餘 developerWorks 用戶交流。
Ted Neward 是 ThoughtWorks 的一名顧問,ThoughtWorks 是一家在全球提供諮詢服務的公司,他仍是 Neward & Associates 的主管,負責有關 Java、.NET 和 XML 服務和其餘平臺的諮詢、指導、培訓和推介。他如今居住在華盛頓西雅圖附近。