程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2EE >> 你不知道的Java秘密(3)

你不知道的Java秘密(3)

編輯:J2EE

Java 平台上更簡單的腳本編寫方法

現在,許多 Java 開發人員都喜歡在 Java 平台中使用腳本語言,但是使用編譯到 Java 字節碼中的動態語言有時是不可行的。在某些情況中,直接編寫一個 Java 應用程序的腳本 部分 或者在一個腳本中調用特定的 Java 對象是更快捷、更高效的方法。

這就是 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

  1. while (true)
  2. {
  3. echo("Hello, world!");
  4. }

由於經常與 Web 浏覽器打交道,許多 Java 開發人員已經知道了 Javascript(或 ECMAScript;JavaScript 是由 Netscape 開發的一種 ECMAScript 語言)。問題是,系統管理員要如何運行這個腳本?

當然,解決方法是 JDK 所帶的 jrunscript 實用程序,如清單 2 所示:

清單 2. jrunscript

  1. C:\developerWorks\5things-scripting\code\JSsrc>jrunscript periodic.JS
  2. Hello, world!
  3. Hello, world!
  4. Hello, world!
  5. Hello, world!
  6. Hello, world!
  7. Hello, world!
  8. Hello, world!
  9. ...

注意,您也可以使用 for 循環按照指定的次數來循環執行這個腳本,然後才退出。基本上,jrunscript 能夠讓您執行 JavaScript 的所有操作。惟一不同的是它的運行環境不是浏覽器,所以運行中不會有 DOM。因此,最頂層的函數和對象稍微有些不同。

因為 Java 6 將 Rhino ECMAScript 引擎作為 JDK 的一部分,jrunscript 可以執行任何傳遞給它的 ECMAScript 代碼,不管是一個文件(如此處所示)或是在更加交互式的 REPL(“Read-Evaluate-Print-Loop”)shell 環境。運行 jrunscript 就可以訪問 REPL shell。

2. 從腳本訪問 Java 對象

能夠編寫 Javascript/ECMAScript 代碼是非常好的,但是我們不希望被迫重新編譯我們在 Java 語言中使用的所有代碼 — 這是違背我們初衷的。幸好,所有使用 Java Scripting API 引擎的代碼都完全能夠訪問整個 Java 生態系統,因為本質上一切代碼都還是 Java 字節碼。所以,回到我們之前的問題,我們可以在 Java 平台上使用傳統的 Runtime.exec() 調用來啟動進程,如清單 3 所示:

清單 3. Runtime.exec() 啟動 jmap

  1. var p = Java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ])
  2. p.waitFor()

數組 arguments 是指向傳遞到這個函數參數的 ECMAScript 標准內置引用。在最頂層的腳本環境中,則是傳遞給腳本本身的的參數數組(命令行參數)。所以,在清單 3 中,這個腳本預期接收一個參數,該參數包含要映射的 Java 進程的 VMID。

除此之外,我們可以利用本身為一個 Java 類的 jmap,然後直接調用它的 main() 方法,如清單 4 所示。有了這個方法,我們不需要 “傳輸” Process 對象的 in/out/err 流。

清單 4. JMap.main()

  1. var args = [ "-histo", arguments[0] ]
  2. Packages.sun.tools.jmap.JMap.main(args)

Packages 語法是一個 Rhino ECMAScript 標識,它指向已經 Rhino 內創建的位於核心 java.* 包之外的 Java 包。

3. 從 Java 代碼調用腳本

從腳本調用 Java 對象僅僅完成了一半的工作:Java 腳本環境也提供了從 Java 代碼調用腳本的功能。這只需要實例化一個 ScriptEngine 對象,然後加載和評估腳本,如清單 5 所示:

清單 5. Java 平台的腳本調用

  1. import Java.io.*;
  2. import Javax.script.*;
  3. public class App
  4. {
  5. public static void main(String[] args)
  6. {
  7. try
  8. {
  9. ScriptEngine engine =
  10. new ScriptEngineManager().getEngineByName("Javascript");
  11. for (String arg : args)
  12. {
  13. FileReader fr = new FileReader(arg);
  14. engine.eval(fr);
  15. }
  16. }
  17. catch(IOException ioEx)
  18. {
  19. ioEx.printStackTrace();
  20. }
  21. catch(ScriptException scrEx)
  22. {
  23. scrEx.printStackTrace();
  24. }
  25. }
  26. }

eval() 方法也可以直接操作一個 String,所以這個腳本不一定必須是文件系統的一個文件 — 它可以來自於數據庫、用戶輸入,或者甚至可以基於環境和用戶操作在應用程序中生成。

4. 將 Java 對象綁定到腳本空間

僅僅調用一個腳本還不夠:腳本通常會與 Java 環境中創建的對象進行交互。這時,Java 主機環境必須創建一些對象並將它們綁定,這樣腳本就可以很容易找到和使用這些對象。這個過程是 ScriptContext 對象的任務,如清單 6 所示:

清單 6. 為腳本綁定對象

  1. import Java.io.*;
  2. import Javax.script.*;
  3. public class App
  4. {
  5. public static void main(String[] args)
  6. {
  7. try
  8. {
  9. ScriptEngine engine =
  10. new ScriptEngineManager().getEngineByName("Javascript");
  11. for (String arg : args)
  12. {
  13. Bindings bindings = new SimpleBindings();
  14. bindings.put("author", new Person("Ted", "Neward", 39));
  15. bindings.put("title", "5 Things You Didn't Know");
  16. FileReader fr = new FileReader(arg);
  17. engine.eval(fr, bindings);
  18. }
  19. }
  20. catch(IOException ioEx)
  21. {
  22. ioEx.printStackTrace();
  23. }
  24. catch(ScriptException scrEx)
  25. {
  26. scrEx.printStackTrace();
  27. }
  28. }
  29. }

訪問所綁定的對象很簡單 — 所綁定對象的名稱是作為全局命名空間引入到腳本的,所以在 Rhino 中使用 Person 很簡單,如清單 7 所示:

清單 7.

  1. println("Hello from inside scripting!")
  2. println("author.firstName = " + author.firstName)

您可以看到,JavaBeans 樣式的屬性被簡化為使用名稱直接訪問,這就好像它們是字段一樣。

5. 編譯頻繁使用的腳本

腳本語言的缺點一直存在於性能方面。其中的原因是,大多數情況下腳本語言是 “即時” 解譯的,因而它在執行時會損失一些解析和驗證文本的時間和 CPU 周期。運行在 JVM 的許多腳本語言最終會將接收的代碼轉換為 Java 字節碼,至少在腳本被第一次解析和驗證時進行轉換;在 Java 程序關閉時,這些即時編譯的代碼會消失。將頻繁使用的腳本保持為字節碼形式可以幫助提升可觀的性能。

我們可以以一種很自然和有意義的方法使用 Java Scripting API。如果返回的 ScriptEngine 實現了 Compilable 接口,那麼這個接口所編譯的方法可用於將腳本(以一個 String 或一個 Reader 傳遞過來的)編譯為一個 CompiledScript 實例,然後它可用於在 eval() 方法中使用不同的綁定重復地處理編譯後的代碼,如清單 8 所示:

清單 8. 編譯解譯後的代碼

  1. import Java.io.*;
  2. import Javax.script.*;
  3. public class App
  4. {
  5. public static void main(String[] args)
  6. {
  7. try
  8. {
  9. ScriptEngine engine =
  10. new ScriptEngineManager().getEngineByName("Javascript");
  11. for (String arg : args)
  12. {
  13. Bindings bindings = new SimpleBindings();
  14. bindings.put("author", new Person("Ted", "Neward", 39));
  15. bindings.put("title", "5 Things You Didn't Know");
  16. FileReader fr = new FileReader(arg);
  17. if (engine instanceof Compilable)
  18. {
  19. System.out.println("Compiling....");
  20. Compilable compEngine = (Compilable)engine;
  21. CompiledScript cs = compEngine.compile(fr);
  22. cs.eval(bindings);
  23. }
  24. else
  25. engine.eval(fr, bindings);
  26. }
  27. }
  28. catch(IOException ioEx)
  29. {
  30. ioEx.printStackTrace();
  31. }
  32. catch(ScriptException scrEx)
  33. {
  34. scrEx.printStackTrace();
  35. }
  36. }
  37. }

在大多數情況中,CompiledScript 實例需要存儲在一個長時間存儲中(例如,servlet-context),這樣才能避免一次次地重復編譯相同的腳本。然而,如果腳本發生變化,您就需要創建一個新的 CompiledScript 來反映這個變化;一旦編譯完成,CompiledScript 就不再執行原始的腳本文件內容。

結束語

Java Scripting API 在擴展 Java 程序的范圍和功能方面前進了很大一步,並且它將腳本語言的編碼效率的優勢帶到 Java 環境。jrunscript — 它顯然不是很難編寫的程序 — 以及 javax.script 給 Java 開發人員帶來了諸如 Ruby (JRuby) 和 ECMAScript (Rhino) 等腳本語言的優勢,同時還不會破壞 Java 環境的生態系統和可擴展性。

關於作者

Ted Neward
Ted Neward

Ted Neward是Neward&Associates的總裁,從事關於Java、.Net、XML Services以及其他平台方面的咨詢、指導和演示等工作。他居住在華盛頓西雅圖。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved