利用動態腳本編寫你的Java應用程序以及重用你的Java類庫
自從計算機誕生以來,軟件開發就傾向於使用高級語言進行開發。從匯編,到C,到C++,再到Java,每一次升級就會面臨來自各界同樣的問題:太慢、而且有太多的Bug、開發者不想放棄對這些原有語言的使用。漸漸地,隨著硬件的快速發展,新的研究和開發技術大大改進了編譯器、解釋器、和虛擬機,開發者不得不向高級語言轉移,放棄他們使用的低級語言開發以提高生產力(將他們從低級語言的障礙中釋放出來以提高他們的生產力)。
Java現在在軟件開發的很多領域裡面占有主導地位,但是在這個發展過程中,動態腳本很有可能無情地取代它的地位。許多年以來,像Python、Perl、Rexx、Groovy、TCL和Ruby這樣的語言能夠在很多專業領域裡面非常出色地工作,例如文件處理、自動測試、軟件構建、代碼重構、和Web圖形頁面設計——他們有著歷史性的名字“腳本語言”。而且在最近的一些年裡,在大多數由Java,C++和其他編譯型計算機語言開發的大型工作裡面,他們也取得了相應的進展。
去年的時候,Ruby on Rails(RoR)Web框架使Ruby有了更進一步的發展。RoR結構利用簡單的Ruby代碼定義了一個典型的多層次Web應用程序——圖形頁面層、業務邏輯層和數據持久層,因此減小了冗余文件、樣本文件代碼、生成的源代碼以及配置文件。RoR框架能夠更加優化更加容易地使用Ruby語言;而且Ruby,這種完善的腳本語言,相對於RoR框架來說可以在更多的領域裡面使用。
作為一個長期的Java開發者,我很可能堅持在一段時間裡一直用Java作開發。但是我仍然保持在我開發的基於Java的系統裡面使用其他的語言,而且Ruby最近顯示出來是特別好的一種候選語言。在JRuby解釋器的幫助下,Ruby和Java一起工作得很好,包括配置、整合、和Java軟件的重用。而且在簡單學習Ruby的過程中也提高了我Java代碼的質量。使用Ruby可以讓我很容易地完成像功能程序和元程序一樣的技術手法,這些技術手法我在Java裡面都是很難實現的。學習這些Ruby裡面的技術手法可以幫助我更好鑒別什麼時候而且怎樣在Java開發中使用它。
這篇文章,我希望能夠和你一起分享我在開發Java系統的時候使用Ruby的那種興奮感。我比較一下Java和Ruby的優點和缺點,而且介紹一下JRuby解釋器的支持者和反對者。而且我會向大家顯示區分Ruby和Java使用的最佳實踐以讓它們各自得到最優化的使用。我會使用一些簡單的代碼來舉例說明這個觀點,並且介紹一個消息實例來展示在Java系統裡面怎樣結合使用Ruby,使其能夠更好地使用動態元程序語言的彈性、表現方式以及功能。
版權聲明:任何獲得Matrix授權的網站,轉載時請務必保留以下作者信息和鏈接
作者:silentbalanceyh;
原文:http://www.matrix.org.cn/resource/article/2006-11-24/Ruby_34bd6112-7b53-11db-99a3-db2d065e2044.Html
關鍵字:Ruby;JRuby
Ruby vs. Java
這篇文章從一個Java開發者的角度解釋了Ruby,主要是集中比較這兩種語言。像Java一樣,Ruby也是一種完全的面向對象的語言。但是這兩種語言有很大的不同。Ruby是動態類型的而且是在源代碼解釋器裡面運行的,這種語言能夠像程序和功能范例一樣支持元編程。我這裡不會介紹Ruby的具體語法,接下來的文章裡面會廣泛地覆蓋其他各個方面。
動態類型
Java有靜態類型。你定義每個變量的類型,接下來在編譯的過程中,如果你使用了類型錯誤的變量將會得到一個編譯時錯誤。Ruby卻相反,擁有動態類型:你不用定義函數和變量的類型,而且沒有到運行的時候不會使用類型檢測,如果你調用一個不存在的方法就會得到錯誤信息。盡管這樣,Ruby不會關心一個對象類型,僅僅看它是否在一個方法裡面調用了這個對象的方法。因為這個原因,這種動態方法可以得到這樣一個duck類型:“如果一個事物走起來像一只鴨子(duck)而且像一只鴨子(duck)呷呷地叫,它就是一只鴨子。”
Listing1.Duck typing
class ADuck
def quack()
puts "quack A";
end
end
class BDuck
def quack()
puts "quack B";
end
end
# quack_it doesn't care about the type of the argument duck, as long
# as it has a method called quack. Classes A and B have no
# inheritance relationship.
def quack_it(duck)
duck.quack
end
a = ADuck.new
b = BDuck.new
quack_it(a)
quack_it(b)
Java也可以通過反射讓你使用動態類型,但是這種笨拙冗長的工作會導致很多混亂的異常發生,像NoSuchMethodError和InvocationTargetException;在實踐中,這些異常傾向於在Java反射的代碼中突然出現,而且相對於Ruby而言出現頻率更高。
即使在沒有使用反射的Java代碼中,你會經常丟失掉靜態類型的信息。比如,在Command設計模式裡面使用execute()方法必須返回Object勝於在Java代碼裡面使用的特殊類型,結果會導致很多ClassCastException發生。同樣的,當在編譯時和運行時修改方法簽名的時候,運行時錯誤就會發生。在實踐開發中,不論是Java還是Ruby,這樣的錯誤很少引起嚴重的程序Bug。一個健壯的單元測試——任何時候你都會用到的——通常都能夠及時捕捉他們。
Ruby的動態類型意思是你不用重復問你自己一個問題:在Java裡面你是否經常在一行裡面遇到這樣冗長的代碼:
XMLPersistence XMLPersistence
= (XMLPersistence)persistenceManager.getPersistence();
Ruby消除了這種對於類型定義和轉換的需要,上邊的代碼用一個典型的Ruby等價表達為;
XMLPersistence = persistence_manager.persistence.
Ruby的動態類型意義上不是弱類型——Ruby經常需要你傳遞正確類型的對象。事實上,Java強制類型轉換比Ruby要弱。例如,Java裡面:”4”+2 等於”42”,這裡會將整數轉化為字符串,在Ruby裡會拋出一個TypeError,告訴你這個“can't convert Fixnum into String.”(Fixnum類型是不可以轉化為String的)。同樣的,Java裡,因為作類型校正犧牲了速度,而且過多地做了整型操作,產生像Integer.MAX_VALUE + 1的整型,和Integer.MIN_VALUE等價,可是Ruby類型校正整型只是在需要的時候。
不論Ruby有什麼優點,Java的靜態類型可以讓它在大規模的項目裡面作為首選:Java工具能夠在開發時候明白代碼意思。IDE能夠在類之間依賴跟蹤,找到方法和類的用處,自動檢標識符而且幫助你檢測代碼。同樣的雖然Ruby工具在這些功能上存在限制,它缺乏類型信息所以不能夠完成上邊這些工作。
解釋性語言
Ruby是在解釋器裡面執行的,所以你不需要等待編譯可以直接測試你的代碼;你能夠很平坦地利用交互方式執行Ruby,執行你鍵入的每一行。除開這些反饋,解釋性語言在處理程序Bug地時候很少有明顯的優點。使用編譯性語言,能夠分析代碼造成的程序Bug,這些領域工程師必須決定發布過的應用程序的確切版本而且需要在源代碼配置管理系統裡面查找這些代碼。在真實生活中,這些通常很簡單。使用解釋性語言,另一方面,分析的時候源代碼是可以馬上利用的而且在需要的時候如果突然異常是可以修復的。
可是解釋性語言因為執行慢有一個不幸的名聲。對比Java的歷史,一種“semi-compiled”語言:在早些年,JVM經常在運行時解釋字節碼,Java因為速度慢捐獻了它的名聲。然而很多應用程序花費了開發者很多時間等待用戶或者是網絡輸入輸出而且這些瓶頸一只保持著,Java開發者快速學習高效算法來優化它強於使用低級計算機語言進行開發。最近幾年裡,Java速度有了一定的提升;例如,基於動態分析的最優化及時編譯器(just-in-time)有時候讓Java勝過C++,這些在基於靜態編譯中最優化是受到限制的。
在很多應用程序中,Ruby不比其他語言速度慢。在不久的將來,Ruby將得到更大的進步就像Ruby本地解釋器轉移到基於字節碼的系統,而且JVM JRuby解釋器獲得了將Ruby編譯成Java字節碼的能力。最終的,任何平台對於很多功能來說都逐漸被忽略,由此獲得了平台獨立性。
功能性編程
Ruby支持熟練的多樣化設計程序范例。另外的對於它的純面向對象的風格,它同樣支持一次性腳本的程序化風格:你可以在類和函數外任何地方寫代碼,就像函數在任何類之外一樣。
更有趣的是,對於Java程序員尋找的新的和有用的觀點,Ruby支持功能范例。你可以在Java裡面使用更多的功能程序結構,使用很多類庫支持就像Jakarta Commons Collections系列的集合包一樣,但是這些語法是笨拙的。Ruby,雖然不是一種純功能性語言,但是對待功能和匿名塊如同完整的語言成員一樣,這些都是能夠像其他簡單對象一樣被傳遞到周圍和利用的。
在Java裡面,你經常反復使用集合的Iterator(迭代器),從邏輯上去遍歷集合裡面的每一個元素。但是這種遍歷僅僅是這樣一個細節實現:一般來說,你僅僅嘗試去接受對集合裡面的每一個元素采取同樣的邏輯。在Ruby裡,你可以將一段代碼塊傳給一個執行它的方法,遍歷是在執行後台運行的。例如,[1,2,3,4,5].each{|n|print n*n, “ “}將打印這樣的字符串:1 4 9 16 25;迭代器將會遍歷這個列表裡面的每一個元素將他作為一個變量n傳到代碼塊裡面。
利用在代碼塊之前和之後執行的指令將代碼塊封裝起來的功能程序是很有用的。比如,在Java裡面,你可以使用Command模式保證一個文件的打開和關閉、數據庫的事務處理、或者其他的資源調用。這些匿名內部類和回調函數式的無用框架將會使代碼變得冗余沉重;除此之外,這裡還有一個變遷規則就是變量在匿名Command類裡面傳遞的時候必須定義成為final。而且為了確保最後的代碼塊在邏輯上能夠執行,整個工作就會使用try{…}finally{…}封裝起來。
在Ruby裡面,你可以封裝任何函數或者代碼塊,不需要任何方法定義或者匿名內部類。在Listing 2裡面,這個文件打開,然後在write()方法執行過後關閉。沒有任何需要使用transaction()方法的代碼和代碼塊。
Listing 2. Wrap a code block
File.open("out.txt", "a") {|f|
f.write("Hello")
}
元程序語言
你一般情況下是定義你的Java類作為你的源程序,但是你仍然可以在運行時進行類定義的操作。這個需要高級技術比如在加載類的時候增加字節碼。比如,Hibernate直接插入數據存取邏輯到商業對象的字節碼裡面,從額外的數據存取層裡保存應用程序。但是類操作,又稱為元程序,在實踐中僅僅是用來組織基礎程序的:應用程序是不能有效地引入這些技巧和脆弱的技術的。
即使在開發的時候,Java限制了開發者改變類的能力。為了加入一個isBlack()方法檢測一個String是否全是空字符串,你不得不加入一個帶有靜態方法的StringUtils 類;理論上,這個新方法是輸入String的。
另一方面,如何在Ruby裡簡單地用一個空白來擴展String類?方法。實際上,因為在Ruby裡面任何東西都是一個對象,你能夠增加Fixnum類,這個等價於Java裡面的原始int類型,如同Listing3所表示的:
Listing 3. Add method to built-in classes String and Fixnum
class String
# Returns true if string is all white space.
# The question mark indicates a Boolean return value.
def blank?()
!(self = /\S/)
end
endclass Fixnum
# Returns 0 or 1 which in Ruby are treated as false and true respectively.
def odd?()
return self % 2
end
endputs " ".blank? # true
# The next line evaluates if-then similarly to Java's ternary Operator ?:
puts (if 23.odd? then "23 odd" else "23 even" end)
JRuby:Java裡的Ruby
作為一個Java程序員,你不要想在產品中使用Ruby直到你能夠讓它和存在的Java應用程序和類庫進行交互,而這些程序和類庫之中能夠支持Ruby的很多種類的基本功能。JRuby,JVM下的一個開源Ruby解釋器,能夠在Java裡面使用Ruby類庫。就像標准的Ruby解釋器一樣,除開使用Ruby調用本地方法(C代碼)或者Java類庫以外,Ruby代碼都能夠在JRuby裡面正確執行。
相比較於微軟的.Net平台的公共語言運行時,JVM往往只能夠支持一種語言。但是事實上,JVM平台不僅僅能夠支持Java,而且可以支持Python、JavaScript、Groovy、Scheme,和其他各種語言,這意味著有必要的時候,Ruby代碼能夠和這些語言很好地進行交互。
在2006年7月中旬,JRuby僅僅有一個預覽版本(0.9)。但是它迅速發展起來:一個志願者團隊從2005年一月開始總共發布了五個版本。JRuby通過針對標准解釋器的不斷評估測試逐漸成熟起來,而且現在已經超過90%的測試都是在基本支持Ruby on Rails這個框架。
為了嘗試JRuby,保證Java SE 5 是安裝好了的而且Java_HOME環境變量也是設置好了的。從JRuby的工程頁面下載壓縮包然後解壓。設置JRUBY_HOME環境變量到JRuby安裝的根目錄。你可以在bin目錄裡面嘗試著用jirb進行交互。大多數場合,你將使用JRuby解釋器——創建一個文件將文件名作為一個參數傳遞到JRuby的bin目錄下批處理腳本。
除了執行先前的Ruby代碼,你仍然可以使用JRuby來構造Java對象,調用Java方法,從一個Java類繼承。一個Ruby類能夠實現Java接口——有必要的話可以在Java裡面靜態調用Ruby方法。
為了從Ruby訪問Java需要初始化類庫,需要以”java”命令開始。接下來用include_class方法指定需要使用的Java類,比如,include_class “javax.jms.Session”。你能夠使用include_package導入整個Java包到Ruby模塊裡面。就像Java導入包的通配符語句一樣,盡量避免include_package使用產生的名稱沖突是明智的;在JRuby裡,如果解釋器為了需要的類搜索所有的包也是格外不明智的。盡可能嚴格地使用include_class。
很多Java標准類的名稱和Ruby類的名稱相同。為了解決這樣的沖突,傳遞一個代碼塊到include_class函數,為這個Java類返回一個新名稱,而且JRuby將使用這個名稱作為Java類的別名。(見Listing4)
Listing 4. Include a Java class with clashing name
require "Java"
# The next line exposes Java's String as JString
include_class("Java.lang.String") { |pkg, name| "J" + name }
s = JString.new("f")
或者,你可以創建一個包含Java類定義的Ruby模塊,但是需要在一個隔離的名稱空間裡面。例如:
Listing 5. Java module importing multiple Java classes
require "Java"
module JavaLang
include_package "Java.lang"
ends = JavaLang::String.new("a")
JRuby的好處是什麼?
像Ruby一樣的動態語言經常使用在專業領域就像整合其他系統一樣;JRuby在Java裡面扮演了這個角色。比如,JRuby能夠從一個系統讀出數據,將這個數據傳遞插入到另外一個系統裡。當需求改變的時候,修改一段JRuby腳本相對於修改配置文件來說簡單得多,所以避免了Java綜合代碼裡面復雜的編譯和發布周期。
除開在Ruby裡使用Java,你也可以從在Java裡使用Ruby,讓你的應用程序容易編寫。利用JRuby的最小化構造語句方法,你可以創建更加容易使用的專業領域語言供用戶工作。比如,一個賭博引擎的腳本系統能夠引入Ruby類來描述字符,媒介和其他游戲實體。
此為,使用Ruby的動態機制,用戶能夠改變腳本類的定義。這些Ruby對象允許直接使用方法管理它的狀態和行為。另外一方面,一般使用用戶配置好的鍵值通過Java映射傳遞,削弱了對象的功能完整性。
Ruby腳本有點像加速過的配置文件。一般Java應用程序的配置都是使用XML文件或者屬性文件,但是這些對於參數定義在開發時間上受到了一定限制。使用Ruby腳本能夠使你的系統要麼從一個文本要麼從一個內置編輯器裡面讀取,用戶能夠自由自定義行為無論什麼情況只要你想放置腳本。這樣的方法,Ruby使用行為結合配置,提供了Java插件API的功能,而且不需要JavaIDE或者編譯器,節省了構建和發布jar文件的步驟。
例如,一個用戶提供的腳本能夠嵌入到一個應用程序事件管理裡面用來對確認的可疑條件進行過濾,然後將發送一個通知給系統管理員以及在一個特定的安全發行數據庫裡面記入日志,或者啟動腳本能夠清除舊文件以及支配用戶數據存儲。同樣的很多富客戶端允許用戶改變菜單和工具條的位置——使用Ruby嵌入,一個用戶的新菜單可以觸發任何用戶想要的行為。
為了方便編寫Java應用程序,Bean Scripting Framework(BSF)在JVM和多種動態語言之間提供了一種標准接口,包括Ruby、Python、BeanShell、Groovy、和Javascript;Java Specification Request(JSR)223,提供BSF的成功規范,將成為Java 6裡面的標准部分。Java代碼能夠發送一個變量到JRuby的名稱空間JRuby可以直接操作這些Java對象或者返回一個值到Java。使用BSF和JSR223,在Java和任何腳本語言之間的用於解釋的語法是相同的。Listing 6顯示了一個BSF使用Ruby的基本例子,這些在線例子的完整代碼放置在bsf_example目錄下。注意BSF不僅包括包外的JRuby支持;但是增加它的簡單指令在JRuby文檔裡面是可使用的。
Listing 6. Embed a Ruby interpreter
...
// JRuby must be registered in BSF.
// jruby.jar and bsf.jar must be on classpath.
BSFManager.registerScriptingEngine("ruby",
"org.jruby.Javasupport.bsf.JRubyEngine", new String[]{"rb"});
BSFManager manager = new BSFManager();
// Make the variable myUrl available from Ruby.
manager.declareBean("myUrl", new URL("http://www/jruby.org"), URL.class);
// Note that the Method getDefaultPort is available from Ruby
// as getDefaultPort and also as defaultPort.
// The following line illustrates the combination of Ruby syntax
// and a Java method call.
String result = (String) manager.eval(
"ruby", "(Java)", 1, 1, "if $myUrl.defaultPort< 1024 then " +
"'System port' else 'User port' end");
在Java裡面直接使用JRuby解釋器也是可能的,但是像這樣會連接你的Java代碼到Ruby規范化的Java封裝類,這些在BSF/JSR223裡面是很嚴格的。
局限性
很重要的一點是你必須記住JRuby仍然在開發中,它還有很多局限,這些在1.0 release版本之前會固定下來。
一個Ruby類是不能從一個Java抽象類繼承的。不幸的是,這種限制使你不能簡單創建一個具體的Java子類,使用虛方法實現抽象方法用於讓Ruby類來繼承。這是因為在Ruby/Java繼承在JRuby的最近的版本中的第二個限制:Java代碼不能多態地調用重寫一個Java方法的Ruby方法。
這些限制讓Swing的使用顯得很困難。比如,通過繼承AbstractTableModel去利用添加到TableModel接口的功能是不可能的。你能夠將繼承轉化為代理繞過這個限制:使用一個具體Java填充類繼承於抽象類作為類型接口的一個代理,這個接口包括所有的Java方法。一個Ruby類實現這個代理接口。盡管這種做法近似於排除了JRuby的平常優勢,但是它提供了基於這個局限的工作區而且在需要的地方提供了彈性:在子類功能裡。
Ruby的標准庫提供了很多方面的功能。這些不使用本地代碼的方式,在JRuby版本中是包含了的。JRuby團隊逐漸地完善很多類庫,雖然一些是在本地代碼上的一個簡單層次,就像GUI類Tk一樣,將不會再完善。包含其中的Java標准庫,提供了很多必要的功能。
JRuby現在在WEBrick Web服務器上提供了Ruby on Rails的基本支持;接下來的一個milestone版本裡面將會在任何Servlet容器裡面實現RoR的支持。RoR支持將使程序員利用一系列存在的Java庫與Web框架的簡易性結合在一起,但是確認JRuby是一種可選的Ruby解釋器。
因為有BUG,JRuby 0.9需要Java SE 5,但是將會在下一個版本中支持JRE 1.4。
例子
例子jms_example.rb舉例說明了JRuby增長長度的用法。它顯示了如何將消息從一個類結構傳到另外一個類,結合兩種不同點的模擬軟件定義了分布式游戲引擎。這些代碼使用先進的功能和元程序技術將任何類型的XML消息作為Ruby對象進行傳輸。代碼注釋可以幫助理解程序邏輯。
在Java裡面使用配置到達這種水平是不可能的;至少,用戶能夠插入編譯代碼。但是在Ruby裡,動態代碼是像靜態代碼一樣自然和容易使用的。
Ruby和Java的未來
Ruby可以教會Java程序員很多。RoR框架顯示了開發Web應用程序是如此的簡單;利用JRuby,RoR將能夠重用存在的Java功能。JRuby將在Java應用程序中加入Jython,JavaScript,和其他各種動態腳本語言。想要掌握這些技巧的開發人員需要學習動態腳本,這些將會覆蓋越來越多的應用程序開發領域。盡管不使用動態腳本,Java開發者也能夠看到Ruby帶來的利益如同功能程序和元程序的概念。