四種Java指令碼語言之評測(轉)

ba發表於2007-08-16
四種Java指令碼語言之評測(轉)[@more@]一、指令碼直譯器概述


在一些Java應用的需求中,整合某種指令碼語言的支援能夠帶來很大的方便。例如,使用者可能想要編寫指令碼程式驅動應用、擴充套件應用,或為了簡化操作而編寫迴圈和其他流程控制邏輯。在這些情況下,一種理想的解決方案是在Java應用中提供對指令碼語言直譯器的支援,讓指令碼語言直譯器讀取使用者編寫的指令碼並在應用提供的類上執行這些指令碼。為了實現這個目標,你可以在Java應用所執行的JVM中,執行一個基於Java的指令碼語言直譯器。


一些支援庫,例如IBM的Bean Scripting Framework,能夠幫助你把不同的指令碼語言整合到Java程式。這些支援框架能夠讓你的Java應用在不作大量修改的情況下,執行Tcl、Python和其他語言編寫的指令碼。


在Java應用中整合了指令碼直譯器之後,使用者編寫的指令碼能夠直接引用Java應用的類,就如這些指令碼屬於Java程式的一部分一樣。這種思路既有優點也有缺點。其優點在於,如果你想要用指令碼驅動的方式對應用進行迴歸測試,或者想要透過指令碼對應用進行低階呼叫,它能夠帶來很大的方便;其缺點在於,如果使用者的指令碼直接操作Java程式的內部結構而不是經過認可的API,它可能影響Java程式的完整性和應用的安全。因此,應當仔細地規劃那些允許使用者針對其編寫指令碼的API,並宣告程式的其餘部分不允許用指令碼操作。另外,你還可以對那些不想讓使用者針對其進行指令碼程式設計的類和方法名稱進行模糊處理,只留出那些允許指令碼程式設計的API類和方法名字。這樣,你就能夠有效地降低喜歡冒險的使用者直接用指令碼操作受保護的類和方法的可能性。


在Java程式中支援多種指令碼語言有著非同尋常的意義,但如果你正在編寫的是一個商業應用,則應當慎重考慮——儘管你為使用者提供了最完善的功能,但同時也帶來了最多的出錯機會。必須考慮到配置和管理問題,因為至少有一部分的指令碼直譯器在定期地進行升級和更新,這樣你就必須花很大的力氣管理各個直譯器的哪些版本適合於Java應用的哪些版本。如果使用者為了解決舊指令碼直譯器中存在的BUG,對其中某個指令碼直譯器進行了升級,你的Java應用就會執行在一種未經完全測試的配置下。數天或數星期之後,使用者也許會發現由於指令碼引擎升級而產生的問題,但他們很可能不會把指令碼引擎升級的事情告訴你,這時你就很難再次重複試驗出使用者報告的錯誤了。


另外,使用者很可能堅持認為你必須為Java應用支援的指令碼直譯器提供補丁。一些指令碼直譯器按照原始碼開放的模式及時進行維護和更新;對於這些指令碼直譯器,可能有專家幫助你解決問題、修補直譯器,或在新的發行版中引入補丁。這是很重要的,因為指令碼直譯器是一個很複雜的工具,包含大量的程式碼,如果沒有專家的支援,對於自己修改指令碼直譯器這一令人煩惱的任務,你很可能束手無策。


為了避免出現這種問題,你應該對於每一種準備在Java應用中提供支援的指令碼直譯器進行全面的測試。對於每一種直譯器,確保它能夠順利地處理絕大多數常見的使用情形,確保它即使在極端苛刻的條件下執行大量的指令碼也不會出現大的記憶體漏洞,確保當你對Java程式和指令碼直譯器進行嚴格的Beta測試時不會出現任何意外的情況。當然,這種前期測試需要投入時間和其他資源;但不管怎樣,測試投入總是物有所值的。
二、保持系統簡潔


如果你必須在Java應用中提供指令碼支援,首先必須選擇一個最符合應用要求和使用者基礎的指令碼直譯器。選擇合適的直譯器能夠簡化整合直譯器的程式碼,減少客戶支援方面的支出,以及提高應用的穩定性。最困難的問題在於:如果只能選用一種直譯器,應該選用哪一種呢?


我比較了幾種指令碼直譯器,開始時考慮的指令碼語言包括Tcl、Python、Perl、JavaScript和BeanShell。接著,在深入分析之前,我放棄了Perl。為什麼呢?因為Perl沒有用Java寫的直譯器。假設你選擇了一個用本機程式碼實現的指令碼直譯器,例如Perl,則Java應用和指令碼程式碼之間的互動就不再直接進行;另外,對於每一個你想要支援的作業系統,都必須提供一個指令碼直譯器的二進位制程式碼庫。由於許多開發者選擇Java是因為看中了它的跨平臺可移植性,為了保證Java應用有這種優點,所以最好選擇一種不依賴於本機程式碼的直譯器。和Perl不同,Tcl、Python、JavaScript和BeanShell都有基於Java的直譯器,所以這些語言的程式碼可以與Java應用在同一個JVM和程式之內執行。


基於以上標準,參與本文評測的指令碼直譯器包括:





Jacl:Tcl的Java實現。


Jython:Python的Java實現。


Rhino:JavaScript的Java實現。


BeanShell:一個用Java編寫的Java原始碼直譯器。




限定了待比較的直譯器種類之後,接下來就可以從各個方面對它們進行比較了。


三、評測之一:可用性


第一個評測專案是可用性。這項評測分析了是否存在某種直譯器不可用的情形。用每一種語言各編寫一個簡單的測試程式,然後分別用相應的直譯器執行,結果發現,所有直譯器都透過了測試,每一種直譯器都能夠穩定地工作或能夠方便地與之互動。既然每一種直譯器都值得考慮,那麼,有哪些因素可能使開發者偏愛其中一種呢?





Jacl:如果你想要在Tk指令碼程式碼中建立使用者介面元素,請訪問Swank project,它把Java的Swing部件封裝到了Tk裡面。發行版不包含Jacl指令碼的偵錯程式。


Jython:支援用Python語法編寫的指令碼。Python利用縮排層次表示程式碼塊的結構,而不是象其他許多語言一樣用花括號或開始-結束符號表示控制流程。至於這種改變究竟是好事還是壞事,這就要看你和使用者的習慣了。發行版不包含Jython指令碼的偵錯程式。


Rhino:許多程式設計師總是把JavaScript和Web頁面程式設計關聯起來,但這個版本的JavaScript不需要在瀏覽器中執行。在使用過程中,我沒有發現任何問題。它的發行版帶有一個簡單但實用的指令碼偵錯程式。


BeanShell:Java程式設計師很快會對這個原始碼直譯器產生一種親切的感覺。BeanShell的文件寫得很不錯,但開發組很小。然而,只有當BeanShell的開發者改變了他們的興趣,卻又沒有其他人填補他們轉換興趣後留下的空白時,開發組太小才會成為一個問題。它的發行版不包含BeanShell指令碼偵錯程式。

四、評測之二:效能


第二個評測專案是效能。這項測試是要分析各個指令碼直譯器執行一些簡單程式的速度。本次測試沒有要求直譯器排序大型陣列,也沒有執行復雜的數學計算,而是執行了一些簡單的、常見的操作,例如迴圈、整數比較,以及分配和初始化大型陣列和二維陣列。測試程式都很簡單,且這些操作都是每一個商業應用或多或少要用到的。另外,本項測試還分析了每一個直譯器初始化和執行簡單指令碼所需要的記憶體。


為一致起見,測試程式的每一種指令碼語言的版本都儘量地相似。測試在一臺Toshiba Tecra 8100筆記本上進行,CPU是700-MHz的Pentium III處理器,RAM是256 MB。呼叫JVM時,堆疊大小使用預設值。


為了便於理解和比較指令碼程式的執行速度,本項評測還在Java 1.3.1下執行了類似功能的Java程式,又在Tcl本機直譯器內執行了為Jacl指令碼直譯器編寫的Tcl指令碼。因此,在下面的表格中,你還可以看到這兩次測試的結果。

表格一:從1到1000000計數的for迴圈:

直譯器型別  時間
-----------------------
Java 10 毫秒
Tcl 1.4 秒
Jacl 140 秒
Jython 1.2 秒
Rhino 5 秒
BeanShell 80 秒
--------------------

表格二:比較整數是否相等,1000000次:

直譯器型別 時間
-----------------------
Java 10 毫秒
Tcl 2 秒
Jacl 300 秒
Jython 4 秒
Rhino 8 秒
BeanShell 80 秒
--------------------

表格三:分配並初始化100000個元素的陣列:

直譯器型別 時間
-----------------------
Java 10 毫秒
Tcl .5 秒
Jacl 25 秒
Jython 1 秒
Rhino 1.3 秒
BeanShell 22 秒
--------------------

表格四:分配並初始化500 X 500 個元素的陣列:

直譯器型別  時間
--------------------
Java 20 毫秒
Tcl 2 秒
Jacl 45 秒
Jython 1 秒
Rhino 7 秒
BeanShell 18 秒
--------------------

表格五:在JVM內初始化直譯器所需要的記憶體:

直譯器型別  記憶體佔用
----------------------
Jacl 大約 1 MB
Jython 大約 2 MB
Rhino 大約 1 MB
BeanShell 大約 1 MB
----------------------


本項評測證明Jython具有最好的效能,與其他直譯器拉開了相當可觀的差距,Rhino第二,BeanShell稍慢,而Jacl墊底。然而,對於你來說,這些效能資料到底能夠產生多大的影響,這與你想要用指令碼語言完成的任務密切相關。如果指令碼函式中包含大量的迭代操作,那麼Jacl或BeanShell可能是令人難以接受的。如果指令碼程式重複執行程式碼的機會很少,那麼這些直譯器在速度上的相對差異就不那麼重要了。值得指出的是,Jython看來沒有為宣告二維陣列提供內建的直接支援,但這個問題可以透過一個“陣列的陣列”結構解決。

五、評測之三:整合的難易程度


本項評測包含兩個任務。第一個任務是比較對各種指令碼語言直譯器進行例項化時需要多少程式碼;第二個任務是編寫一個完成如下操作的指令碼:例項化一個Java JFrame,放入一個JTree,調整大小並顯示出JFrame。儘管這些任務都很簡單,但由此我們可以看出開始使用一個直譯器要做多少工作,還可以看出為直譯器編寫的指令碼程式碼在呼叫Java類時到底是什麼樣子。


■ Jacl

要把Jacl整合到Java應用,首先要把Jacl的Jar檔案加入到Java的CLASSPATH,然後在執行指令碼之前,建立Jacl直譯器的例項。下面是建立Jacl直譯器例項的程式碼:



import tcl.lang.*;public class SimpleEmbedded { public static void main(String args[]) { try { Interp interp = new Interp(); } catch (Exception e) { }}


下面的Jacl指令碼程式碼顯示瞭如何建立一個JTree,把它放入JFrame,調整大小並顯示JFrame:



package require javaset env(TCL_CLASSPATH) set mid [java::new javax.swing.JTree]set f [java::new javax.swing.JFrame]$f setSize 200 200set layout [java::new java.awt.BorderLayout]$f setLayout $layout$f add $mid $f show



■ Jython

要把Jython整合到Java應用,首先要把Jython的Jar檔案加入到Java的CLASSPATH,然後在執行指令碼之前,建立一個Jython直譯器的例項。完成這個任務的程式碼很簡單:



import org.python.util.PythonInterpreter;import org.python.core.*;public class SimpleEmbedded { public static void main(String []args) throws PyException { PythonInterpreter interp = new PythonInterpreter(); }}


下面的Jython指令碼程式碼顯示瞭如何建立JTree,把它放入JFrame,然後顯示出JFrame。下面的程式碼不包含調整大小的操作:



from pawt import swingimport java, sysframe = swing.JFrame('Jython example', visible=1)tree = swing.JTree()frame.contentPane.add(tree)frame.pack()


■ Rhino


和其他直譯器一樣,整合Rhino時首先要把Rhino的Jar檔案加入到Java的CLASSPATH,然後在執行指令碼之前,建立Rhino直譯器的例項:



import org.mozilla.javascript.*;import org.mozilla.javascript.tools.ToolErrorReporter;public class SimpleEmbedded { public static void main(String args[]) { Context cx = Context.enter(); }}


下面簡單的Rhino指令碼顯示瞭如何建立JTree,把它放入JFrame,調整大小並顯示出JFrame:



importPackage(java.awt);importPackage(Packages.javax.swing);frame = new Frame("JavaScript");frame.setSize(new Dimension(200,200)); frame.setLayout(new BorderLayout());t = new JTree();frame.add(t, BorderLayout.CENTER);frame.pack();frame.show();


■ BeanShell


整合BeanShell也和整合其他直譯器一樣簡單。先把BeanShell的Jar檔案加入到Java的CLASSPATH,然後在執行指令碼程式碼之前建立一個BeanShell直譯器的例項:



import bsh.Interpreter;public class SimpleEmbedded { public static void main(String []args) throws bsh.EvalError { Interpreter i = new Interpreter(); }}


下面的BeanShell指令碼程式碼顯示瞭如何建立一個JTree,把它放入JFrame,調整大小並顯示出JFrame。程式碼很簡單,且具有熟悉的Java風格:



frame = new JFrame();tree = new JTree();frame.getContentPane().add(tree);frame.pack();frame.show();


從上面的說明可以看出,在Java應用中整合任何一種直譯器都是很容易的。同時,只要你掌握了指令碼語言的語法,就能夠高效地編寫出指令碼程式。前面幾個簡單的例子顯示出,用BeanShell和JavaScript編寫的指令碼在格式上與Java最相似,而Jacl和Jython則顯得有些不同,但Jacl和Jython指令碼也不是難以理解的。正如上面為各個指令碼直譯器編寫的指令碼所顯示的,在指令碼程式碼和Java應用的類之間不存在任何防火牆。因此必須注意:指令碼程式碼直接在Java應用的類的基礎上執行。應當確信這就是你想要的效果。如果你想要在執行時對應用的某些部分進行保護,避免指令碼程式碼訪問某些部分,就應當採取對非公開的程式碼進行模糊處理之類的措施,避免人們直接對不公開的API進行程式設計。

六、評測之四:支援和許可問題


儘管整合指令碼直譯器賦予Java應用額外的能力,但同時它也使得Java應用依賴於那些指令碼庫。在確定選用某一種指令碼直譯器之前,考慮一下將來的某一天你必須修改被整合的程式碼的機會。如果指令碼直譯器的開發者很少更新或升級直譯器,這不是一個好的跡象。它或者意味著當時的直譯器實現程式碼已經很完美,或者負責這些程式碼的開發者已經轉移到其他軟體專案上。至於哪一種情況的可能性比較大,答案非常明顯。


另外,還有必要看看實現直譯器需要多少原始碼。試圖掌握直譯器的每一行程式碼並對它進行擴充套件或改進是不切實際的,因為直譯器的程式碼規模實在太大了。儘管如此,瞭解直譯器的規模仍是必要的,因為在某些時候,你可能需要修改直譯器的原始碼,也可能為了掌握直譯器的具體工作原理而需要對直譯器程式碼作比較深入的瞭解。


下面就來看看每一種直譯器的程式碼庫支援問題。





Jacl


Jacl有一個活躍的支援和開發組。儘管開發網站上的下載連結指向了一個數年前的發行版,但新的版本可透過CVS版本控制系統找到。Jacl包含約37000行Java程式碼。


Jython


Jython的支援、維護和更新看起來都很活躍。Jython大約包含55000行Java程式碼。


Rhino


Rhino的更新和發行都比較頻繁,它大約包含44000行Java程式碼。


BeanShell


BeanShell也定期地進行更新,它大約包含25000行Java程式碼,另外還有不少BeanShell指令碼提供。





可以看出,所有這些直譯器都很龐大。如果你能夠依賴於直譯器的開發和支援組織提供的改進和BUG補丁,你自己的麻煩就會少一些。在選擇一個直譯器之前,不妨看看直譯器升級和發行是否很頻繁。也許你可以與某個開發者取得聯絡,瞭解他們的長遠計劃以及BUG修正過程。


這些直譯器都是可以免費下載的。然而,如果要把它們嵌入到商業應用之中,它們的許可規則又是怎樣的呢?好在對於所有這些直譯器來說,軟體許可都不存在什麼問題。閱讀Jacl、Jython、JavaScript和BeanShell的許可協議可以發現,使用者必須遵從GNU LGP或等價的許可。這就意味著,即使你的Java應用不是免費的,仍舊可以在釋出應用時帶上指令碼直譯器。但是,你不能刪除原始碼檔案和指令碼檔案中的版權資訊,而且還要明確地告訴使用者,與Java應用捆綁在一起的指令碼直譯器屬於其他人所有。



七、結束語


如果你打算在Java應用中整合指令碼程式設計支援,我建議你只選用一個指令碼直譯器。在你的產品中,每次額外增加一種指令碼支援都會帶來相應的代價,因此應該避免在Java應用中整合一種以上的指令碼直譯器。為Java應用新增指令碼支援時,選用基於Java的直譯器而不是Perl之類的本機直譯器能夠簡化以後的工作,能夠使你的產品具有更好的可移植性,併為Java程式和直譯器的整合工作帶來方便。


如果客戶想要用某種特定的指令碼語言來定製你的產品,務必認真地檢查一下如果整合了支援該語言的指令碼直譯器是否會出現問題。如果你不必侷限於某種特定的指令碼語言,則應當從多個不同的角度對直譯器進行比較,看看哪一個更適合Java應用所面臨的主要任務。


例如,與其他直譯器相比,Jacl的發展速度看起來特別慢,但如果你必須使用Tcl指令碼,使用Jacl直譯器仍舊是值得的。如果你要把一個應用從Tcl/Tk移植到Java,Jacl使得新的Java應用能夠執行原來的Tcl指令碼,這種能力的價值可能超越其他方面的不足。另外,Tcl屬於流行的程式語言,很多開發者已經熟悉它,而且關於Tcl程式設計的書也容易買到。


如果你喜歡Java風格的指令碼程式碼,並且力求減少整合過程中的麻煩,BeanShell看來很不錯。它的不足之處是,BeanShell語法和程式設計方面的使用者指南僅僅侷限於發行版所包含的內容,而且BeanShell與其他一些指令碼直譯器相比執行速度較慢。另一方面,我覺得BeanShell比較容易使用。BeanShell的庫組織得很好,從而簡化了整合工作。如果你選擇指令碼直譯器時效能不是關鍵的考慮因素,那麼你可以考慮BeanShell。


Rhino執行速度明顯比BeanShell快,而且它也同樣支援Java風格的指令碼。另外,它看起來具有較高的開發質量和支援服務,有關JavaScript語法和程式設計的書也很容易找到。如果你對效能、Java風格的語法和強大的支援服務有著差不多平衡的需求, Rhino無疑是推薦考慮的。




在本文評測的四種指令碼直譯器中,Jython是速度最快的一種,擁有一些強大的程式設計功能。唯一真正令人擔心的是Jyphon的流程控制語法,不過,你可能會在乎這些語法上的差異,也可能不會在乎。就象Jacl一樣,由於需要學習的新知識比較多,用Jython編寫指令碼可能需要比JavaScript和BeanShell更長的學習時間。如果你想要用Python編寫比較複雜的指令碼,就應該買一本書。Python是一種廣受歡迎的程式語言,因此可供選擇的書籍也相當多。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-961685/,如需轉載,請註明出處,否則將追究法律責任。

相關文章