你不知道的Java祕密

當應用程序性能受到損害時,大多數開發人員都驚慌失措,這在情理之中。跟蹤Java應用程序瓶頸來源一直以來都是很麻煩的,因爲Java虛擬機有黑盒效應,而且Java平臺分析工具一貫就有缺陷。

然而,隨着Java5中JConsole的引入,一切都發生了改變。JConsole是一個內置Java性能分析器,可以從命令行或在GUIshell中運行。它不是完美的,但是當尖頭老闆來問你關於性能的問題時,用它來應對還是綽綽有餘的——這比查詢PapaGoogle要好得多。

我們將向您展示5個方法,使您可以輕鬆地使用JConsole(或者,它更高端的“近親”VisualVM)來監控Java應用程序性能和跟蹤Java中的代碼。

1.JDK附帶分析器

許多開發人員沒有意識到從Java 5開始JDK中包含了一個分析器。JConsole(或者Java平臺最新版本,VisualVM)是一個內置分析器,它同Java編譯器一樣容易啓動。如果是從命令行啓動,使JDK在PATH上,運行jconsole即可。如果從GUIshell啓動,找到JDK安裝路徑,打開bin文件夾,雙擊jconsole。

當分析工具彈出時(取決於正在運行的Java版本以及正在運行的Java程序數量),可能會出現一個對話框,要求輸入一個進程的URL來連接,也可能列出許多不同的本地Java進程(有時包含JConsole進程本身)來連接。

使用JConsole進行工作

在Java 5中,Java進程並不是被設置爲默認分析的,而是通過一個命令行參數—-Dcom.sun.management.jmxremote——在啓動時告訴Java 5 VM打開連接,以便分析器可以找到它們;當進程被JConsole撿起時,您只能雙擊它開始分析。

分析器有自己的開銷,因此最好的辦法就是花點時間來弄清是什麼開銷。發現JConsole開銷最簡單的辦法是,首先獨自運行一個應用程序,然後在分析器下運行,並測量差異。(應用程序不能太大或者太小;我最喜歡使用JDK附帶的SwingSet2樣本。)因此,我使用-verbose:gc嘗試運行SwingSet2來查看垃圾收集清理,然後運行同一個應用程序並將JConsole分析器連接到它。當JConsole連接好了之後,一個穩定的GC清理流出現,否則不會出現。這就是分析器的性能開銷。

JConsole或VisualVM?

JConsole從Java 5開始就隨着Java平臺版本一起發佈,而VisualVM是在NetBeans基礎上升級的一個分析器,在Java 6的更新版12中第一次發佈。多數還沒有更新到Java 6,因此這篇文章主要介紹JConsole。然而,多數技巧和這兩個分析器都有關。

2.遠程連接進程

因爲Web應用程序分析工具假設通過一個套接字進行連通性分析,您只需要進行少許配置來設置JConsole(或者是基於JVMTI的分析器,就這點而言),監控/分析遠程運行的應用程序。

如果Tomcat運行在一個名爲“webserve”的機器上,且JVM已經啓動了JMX並監聽端口9004,從JConsole(或者任何JMX客戶端)連接它需要一個JMX URL“service:jmx:rmi:///jndi/rmi://webserver:9004/jmxrmi”。

基本上,要分析一個運行在遠程數據中心的應用程序服務器,您所需要的僅僅是一個JMX URL。

3.跟蹤統計

JConsole有許多對收集統計數據有用的選項卡,包括:

◆Memory:在JVM垃圾收集器中針對各個堆跟蹤活動。

◆Threads:在目標JVM中檢查當前線程活動。

◆Classes:觀察VM已加載類的總數。

這些選項卡(和相關的圖表)都是由每個Java 5及更高版本VM在JMX服務器上註冊的JMX對象提供的,是內置到JVM的。一個給定JVM中可用bean的完整清單在MBeans選項卡上列出,包括一些元數據和一個有限的用戶界面來查看數據或執行操作。(然而,註冊通知是在JConsole用戶界面之外。)

使用統計數據

假設一個Tomcat進程死於OutOfMemoryError。如果您想要弄清楚發生了什麼,打開JConsole,單擊Classes選項卡,過一段時間查看一次類計數。如果數量穩定上升,您可以假設應用程序服務器或者您的代碼某個地方有一個ClassLoader漏洞,不久之後將耗盡PermGen空間。如果需要更進一步的確認問題,請看Memory選項卡。

不要成爲典型

發現應用程序代碼中性能問題的常用響應多種多樣,但也是可預測的。早期的Java編程人員對舊的IDE可能十分生氣,並開始進行代碼庫中主要部分的代碼複查,在源代碼中尋找熟悉的“紅色標誌”,像異步塊、對象配額等等。隨着編程經驗的增加,開發人員可能會仔細研究JVM支持的-X標誌,尋找優化垃圾收集器的方法。當然,對於新手,直接去Google查詢,希望有其他人發現了JVM的神奇的“make it go fast”轉換,避免重寫代碼。

從本質上來說,這些方法沒什麼錯,但都是有風險的。對於一個性能問題最有效的響應就是使用一個分析器——現在它們內置在Java平臺,我們確實沒有理由不這樣做!

4.爲離線分析創建一個堆轉儲

生產環境中一切都在快速地進行着,您可能沒有時間花費在您的應用程序分析器上,相反地,您可以爲Java環境中的每個事件照一個快照保存下來過後再看。在JConsole中您也可以這樣做,在VisualVM中甚至會做得更好。
先找到MBeans選項卡,在其中打開com.sun.management節點,接着是HotSpotDiagnostic節點。現在,選擇Operations,注意右邊面板中的“dumpHeap”按鈕。如果您在第一個(“字符串”)輸入框中向dumpHeap傳遞一個文件名來轉儲,它將爲整個JVM堆照一個快照,並將其轉儲到那個文件。

稍後,您可以使用各種不同的商業分析器來分析文件,或者使用VisualVM分析快照。(記住,VisualVM是在Java 6中可用的,且是單獨下載的。)

5.JConsole並不是高深莫測的

作爲一個分析器實用工具,JConsole是極好的,但是還有更好的工具。一些分析插件附帶分析器或者靈巧的用戶界面,默認情況下比JConsole跟蹤更多的數據。

JConsole真正吸引人的是整個程序是用“普通舊式Java”編寫的,這意味着任何Java開發人員都可以編寫這樣一個實用工具。事實上,JDK其中甚至包括如何通過創建一個插件來定製JConsole的示例。建立在NetBeans頂部的VisualVM進一步延伸了插件概念。

如果JConsole(或者VisualVM,或者其他任何工具)不符合您的需求,或者不能跟蹤您想要跟蹤的,或者不能按照您的方式跟蹤,您可以編寫屬於自己的工具。如果您覺得Java代碼很麻煩,Groovy或JRuby或很多其他JVM語言都可以幫助您更快完成。

您真正需要的是一個快速而粗糙(quick-and-dirty)的由JVM連接的命令行工具,可以以您想要的方式確切地跟蹤您感興趣的數據。

5個命令行分析工具

全功能內置分析器,如JConsole和VisualVM的成本有時比它們的性能費用還要高—尤其是在生產軟件上運行的系統中。因此,在聚焦Java性能監控的第2篇中,我將介紹5個命令行分析工具,使開發人員僅關注運行的Java進程的一個方面。

JDK包括很多命令行實用程序,可以用於監控和管理Java應用程序性能。雖然大多數這類應用程序都被標註爲“實驗型”,在技術上不受支持,但是它們很有用。

1.jps(sun.tools.jps)

很多命令行工具都要求您識別您希望監控的Java進程。這與監控本地操作系統進程、同樣需要一個程序識別器的同類工具沒有太大區別。

“VMID”識別器與本地操作系統進程識別器(“pid”)並不總是相同的,這就是我們需要JDKjps實用程序的原因。

在Java進程中使用jps

與配置JDK的大部分工具及本文中提及的所有工具一樣,可執行jps通常是一個圍繞Java類或執行大多數工作的類集的一個薄包裝。在Windows®環境下,這些工具是.exe文件,使用JNIInvocationAPI直接調用上面提及的類;在UNIX®環境下,大多數工具是一個shell腳本的符號鏈接,該腳本採用指定的正確類名稱開始一個普通啓動程序。如果您希望在Java進程中使用jps(或者任何其他工具)的功能—Ant腳本—僅在每個工具的“主”類上調用main()相對容易。爲了簡化引用,類名稱出現在每個工具名稱之後的括號內。

jps—名稱反映了在大多數UNIX系統上發現的ps實用程序—告訴我們運行Java應用程序的JVMID。顧名思義,jps返回指定機器上運行的所有已發現的Java進程的VMID。如果jps沒有發現進程,並不意味着無法附加或研究Java進程,而只是意味着它並未宣傳自己的可用性。

如果發現Java進程,jps將列出啓用它的命令行。這種區分Java進程的方法非常重要,因爲只要涉及操作系統,所有的Java進程都被統稱爲“java”。在大多數情況下,VMID是值得注意的重要數字。

使用分析器開始

使用分析實用程序開始的最簡單方法是使用一個如在demo/jfc/SwingSet2中發現的SwingSet2演示一樣的演示程序。這樣就可以避免程序作爲背景/監控程序運行時出現掛起的可能性。當您瞭解工具及其費用後,就可以在實際程序中進行試用。

加載演示應用程序後,運行jps並注意返回的vmid。爲了獲得更好的效果,採用-Dcom.sun.management.jmxremote屬性集啓動Java進程。如果沒有使用該設置,部分下列工具收集的部分數據可能不可用。

2.jstat(sun.tools.jstat)

jstat實用程序可以用於收集各種各樣不同的統計數據。jstat統計數據被分類到“選項”中,這些選項在命令行中被指定作爲第一參數。對於JDK 1.6來說,您可以通過採用命令-options運行jstat查看可用的選項清單。清單1中顯示了部分選項:

清單1.jstat選項

  1.  
  2.  
  3. -class  
  4. -compiler  
  5. -gc  
  6. -gccapacity  
  7. -gccause  
  8. -gcnew  
  9. -gcnewcapacity  
  10. -gcold  
  11. -gcoldcapacity  
  12. -gcpermcapacity  
  13. -gcutil  
  14. -printcompilation   

實用程序的JDK記錄將告訴您清單1中每個選項返回的內容,但是其中大多數用於收集垃圾的收集器或者其部件的性能信息。-class選項顯示了加載及未加載的類(使其成爲檢測應用程序服務器或代碼中ClassLoader泄露的重要實用程序,且-compiler和-printcompilation都顯示了有關Hotspot JIT編譯程序的信息。

默認情況下,jstat在您覈對信息時顯示信息。如果您希望每隔一定時間拍攝快照,請在-options指令後以毫秒爲單位指定間隔時間。jstat將持續顯示監控進程信息的快照。如果您希望jstat在終止前進行特定數量的快照,在間隔時間/時間值後指定該數字。

如果5756是幾分鐘前開始的運行SwingSet2程序的VMID,那麼下列命令將告訴jstat每250毫秒爲10個佚代執行一次gc快照轉儲,然後停止:

  1. jstat -gc 5756 250 10 

請注意Sun(現在的Oracle)保留了在不進行任何預先通知的情況下更改各種選項的輸出甚至是選項本身的權利。這是使用不受支持實用程序的缺點。請參看Javadocs瞭解jstat輸出中每一列的全部細節。

3.jstack(sun.tools.jstack)

瞭解Java進程及其對應的執行線程內部發生的情況是一種常見的診斷挑戰。例如,當一個應用程序突然停止進程時,很明顯出現了資源耗盡,但是僅通過查看代碼無法明確知道何處出現資源耗盡,且爲什麼會發生。

jstack是一個可以返回在應用程序上運行的各種各樣線程的一個完整轉儲的實用程序,您可以使用它查明問題。

採用期望進程的VMID運行jstack會產生一個堆轉儲。就這一點而言,jstack與在控制檯窗口內按Ctrl-Break鍵起同樣的作用,在控制檯窗口中,Java進程正在運行或調用VM內每個Thread對象上的Thread.getAllStackTraces()或Thread.dumpStack()。jstack調用也轉儲關於在VM內運行的非Java線程的信息,這些線程作爲Thread對象並不總是可用的。

jstack的-l參數提供了一個較長的轉儲,包括關於每個Java線程持有鎖的更多詳細信息,因此發現(和squash)死鎖或可伸縮性bug是極其重要的。

4.jmap(sun.tools.jmap)

有時,您正在處理的問題是一個對象泄露,如一個ArrayList(可能持有成千上萬個對象)該釋放時沒有釋放。另一個更普遍的問題是,看似從不會壓縮的擴展堆,卻有活躍的垃圾收集。

當您努力尋找一個對象泄露時,在指定時刻對堆及時進行拍照,然後審查其中內容非常有用。jmap通過對堆拍攝快照來提供該功能的第一部分。然後您可以採用下一部分中描述的jhat實用程序分析堆數據。

與這裏描述的其他所有實用程序一樣,使用jmap非常簡單。將jmap指向您希望拍快照的Java進程的VMID,然後給予它部分參數,用來描述產生的結果文件。您要傳遞給jmap的選項包括轉儲文件的名稱以及是否使用一個文本文件或二進制文件。二進制文件是最有用的選項,但是隻有當與某一種索引工具結合使用時—通過十六進制值的文本手動操作數百兆字節不是最好的方法。

隨意看一下Java堆的更多信息,jmap同樣支持-histo選項。-histo產生一個對象文本柱狀圖,現在在堆中大量引用,由特定類型消耗的字節總數分類。它同樣給出了特定類型的總示例數量,支持部分原始計算,並猜測每個實例的相對成本。

不幸的是,jmap沒有像jstat一樣的period-and-max-count選項,但是將jmap(或jmap.main())調用放入shell腳本或其他類的循環,週期性地拍攝快照相對簡單。(事實上,這是加入jmap的一個好的擴展,不管是作爲OpenJDK本身的源補丁,還是作爲其他實用程序的擴展。)

5.jhat(com.sun.tools.hat.Main)

將堆轉儲至一個二進制文件後,您就可以使用jhat分析二進制堆轉儲文件。jhat創建一個HTTP/HTML服務器,該服務器可以在瀏覽器中被瀏覽,提供一個關於堆的object-by-object視圖,及時凍結。根據對象引用草率處理堆可能會非常可笑,您可以通過對總體混亂進行某種自動分析而獲得更好的服務。幸運的是,jhat支持OQL語法進行這樣的分析。

例如,對所有含有超過100個字符的String運行OQL查詢看起來如下:

  1. select s from java.lang.String s where s.count >= 100 

結果作爲對象鏈接顯示,然後展示該對象的完整內容,字段引用作爲可以解除引用的其他鏈接的其他對象。OQL查詢同樣可以調用對象的方法,將正則表達式作爲查詢的一部分,並使用內置查詢工具。一種查詢工具,referrers()函數,顯示了引用指定類型對象的所有引用。下面是尋找所有參考File對象的查詢:

  1. select referrers(f) from java.io.File f  

您可以查找OQL的完整語法及其在jhat瀏覽器環境內“OQL Help”頁面上的特性。將jhat與OQL相結合是對行爲不當的堆進行對象調查的有效方法。

結束語

當您需要近距離觀察Java進程內發生的事情時,JDK的分析擴展會非常有用。本文中介紹的所有工具都可以從命令行中由其自己使用。它們還可以與JConsole或VisualVM有力地結合使用。JConsole和VisualVM提供Java虛擬機的總體視圖,jstat和jmap等有針對性的工具支持您對研究進行微調。

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.  
  2.  
  3. while (true)  
  4. {  
  5.     echo("Hello, world!");  
  6. }  

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

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

清單 2. jrunscript

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

注意,您也可以使用 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.  
  2.  
  3. var p = java.lang.Runtime.getRuntime().exec("jmap", [ "-histo", arguments[0] ])  
  4. p.waitFor()  

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

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

清單 4. JMap.main()

  1.  
  2.       
  3. var args = [ "-histo", arguments[0] ]  
  4. 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.  
  4. public class App  
  5. {  
  6.     public static void main(String[] args)  
  7.     {  
  8.         try  
  9.         {  
  10.             ScriptEngine engine =   
  11.                 new ScriptEngineManager().getEngineByName("javascript");  
  12.             for (String arg : args)  
  13.             {  
  14.                 FileReader fr = new FileReader(arg);  
  15.                 engine.eval(fr);  
  16.             }  
  17.         }  
  18.         catch(IOException ioEx)  
  19.         {  
  20.             ioEx.printStackTrace();  
  21.         }  
  22.         catch(ScriptException scrEx)  
  23.         {  
  24.             scrEx.printStackTrace();  
  25.         }  
  26.     }  
  27. }  

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

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

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

清單 6. 爲腳本綁定對象

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

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

清單 7. 

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

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

5. 編譯頻繁使用的腳本

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

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

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

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

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

結束語

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章