你通常需要一個命令來進入Ruby和Java的聯合世界:
include Java
這使你可以實例化Java類,調用其方法,甚至繼承它們,就好象它們僅僅是普通的Ruby對象一樣。但這其中有一些微妙的差異,這篇文章將向你展示如何管理它們以便能以最快的速度設計出新的應用並部署到你的客戶那裡。
這篇文章基於一個簡單的應用,該應用使用JRuby和Swing實現了一個簡單的ObjectSpace浏覽器。Ruby的ObjectSpace特性提供了一種方式來訪問系統中所有對象。例如,我們可以這樣打印所有使用中的字符串:
ObjectSpace.each_object(String) do |string|
puts string
end
當該程序在我的irb會話中運行時,大約會產生28000個字符串。通過使用Swing和JRuby,我們可以把不同的類顯示在一個非常棒的圖形界面上,包括它們的實例以及可用的方法。你甚至可以在最右邊的面板中點擊它們來調用無參的方法:
JRuby的ObjectSpace支持在默認情況下是被禁用的,這是由於它在運行時所產生的性能問題,當然還有其他原因。
我要指出它在實現中的一些有趣的細節,並就如何開始使用JRuby中的Java集成特性給出一些提示。
Java集成
一旦你將Java集成到腳本中,你就可以繼承現有的Java類。我們只需要通過指定Java類的完整限定名就可以做到這一點。在這個例子應用中,主窗口繼承了JFrame。該類中還包含進了javax.swing和java.awt包,所以你不必每次都使用類的全名。
class MainWindow < javax.swing.JFrame
include_package 'javax.swing'
include_package 'java.awt' ...
做為另外一種選擇,你還可以使用include_class功能包含指定的類,這樣就不會因你沒有使用某些類,而污染了名稱空間。
調用父類的構造方法就和普通的Ruby代碼一樣,這意味著我們可以在initialize方法的第一行通過調用super("JRuby Object Browser")來設定窗口的標題。
因為類包含了整個javax.swing包,所以實例化Java類就變得非常直接:
list_panel = JPanel.new
list_panel.layout = GridLayout.new(0, 3)
如果你仔細看看第二行,你可能會覺得我們直接訪問了JPanel的layout屬性,但事實並非如此。JRuby為Java對象增加了一些便捷的方法,所以上面的語句也可以用我們熟知的方式編寫:
list_panel.setLayout(GridLayout.new(0, 3))
不再使用getters和setters,看起來你可以直接訪問屬性。JRuby還向Java對象增加了更多的“語法糖”,這使得整個感覺更像Ruby了:對於任何方法調用,你可以使用snake_case符號而不是為任何方法調用而實際定義的camel case名。由此產生出調用setLayout方法的第三種方式:
list_panel.set_layout(GridLayout.new(0, 3))
從類中調用setter方法時要小心,Ruby可能會認為你想創建一個局部變量,所以你必須以self作為接收者來顯式地調用它:
self.default_close_operation = WindowConstants::EXIT_ON_CLOSE
Java和Ruby的另一個區別是對於常量的訪問,像之前代碼片斷中的EXIT_ON_CLOSE。記住,當你將Java代碼轉化為Ruby時,要將所有的.訪問符替換為::。
到目前為止,JRuby對於Swing開發所帶來的變化看起來好像也沒什麼,但是我們尚未涉及到一個非常重要的方面:監聽器。在Java中,將一個監聽器與事件關聯起來通常需要在一個匿名類中實現一個接口:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
...
}
});
如果你想附加很多監聽器時這很容易搞亂你的代碼。在JRuby中,以下兩行代碼就能解決問題(如果你不使用event變量,你甚至可以忽略掉它):
button.add_action_listener do |event|
...
end
這些是你使用JRuby開發Swing時需要掌握的基礎。盡管JRuby使得用Swing開發GUI變得更方便,但是你還是需要手寫很多代碼,當需要復雜布局時更是如此。如果你希望能更簡單地創建Swing UI時,請參考使用JRuby GUI APIs的三種方式 。
部署JRuby應用變得更簡單
Ruby應用和庫通常是通過RubyGems分發的,但是為了利用其優勢,你需要安裝好Ruby和RubyGems,而這對於一般的用戶來說卻並不實際。對於傳統的(MRI / C-Ruby)程序,該問題已經通過RubyScript2Exe[1]的方式得到了解決,這是通過將腳本和一個Ruby解釋器綁定到一個可運行在多個平台上的包裡面來實現的。JRuby的用戶也不必為此感到沮喪,相反,他們手頭上已經有了一個更加強大的工具來快速部署應用:Java Web Start。
使用Java Web Start
Java Web Start包含在Java運行時環境中,因此在大多數系統中都可以使用。使用Web Start部署應用相當簡單,所需要的僅僅是一個包含所有文件和JNLP(Java Network Launching Protocol)描述文件的jar包。我們在ObjectSpace浏覽器應用的基礎上,來示范一下如何創建一個可以通過web-start進行部署的Ruby應用。
使用Web Start的前提是一個包含應用的jar包,我們首先從它開始。JRuby提供了兩種不同的庫:“最小的”jruby.jar和jruby-complete.jar,後者捆綁了整個Ruby標准庫。如果你不使用標准庫,那麼你可以使用更小的jruby.jar,這樣可以減少大約1M的下載量。
讓你的腳本運行的最簡單的方式就是將.rb文件添加到jruby.jar。下面的命令將我們例子中的rob.rb增加到壓縮包中。
jar uf jruby.jar rob.rb
你可以通過java來啟動應用,來檢查上面的命令是否正確,而這需要我們的Ruby腳本。這個應用程序需要ObjectSpace,我們可以通過向Java傳遞jruby.objectspace.enabled=true屬性來激活它。
java -Djruby.objectspace.enabled=true -jar jruby.jar -r rob
-r選項自動尋找所需文件,然後運行我們的腳本。
提早編譯
JRuby1.1的一個令人激動的新特性就是對提前(ahead of time,即AOT)編譯的支持。現在JRuby中有2048個方法采用即時編譯,而提前編譯能減輕這一限制。JRuby編譯器jrubyc目前仍處在開發中,所以我建議使用最新的JRuby版本。將普通的Ruby文件編譯為class文件就像通過腳本參數調用編譯器那樣簡單:
jrubyc rob.rb
這會創建一個包含rob.class文件的ruby目錄。這次不再需要像我們之前那樣將ruby目錄打包到jruby.jar中,而只需要創建一個單獨的Jar來包含應用程序。畢竟修改現存的Jar看起來並不是一個優雅的方案。我們可以使用同名的工具來創建Jar:
jar -cfe rob.jar ruby/rob.class ruby
這會創建一個包含我們的類的名為rob.jar的小的jar文件,並且在Manifest中指定ruby/rob.class為主類。這使得我們可以簡化調用,因為我們現在可以很簡單地指向類,而無需使用命令行。為了執行它,我們要確保rob.jar在classpath上:
java -Djruby.objectspace.enabled=true -cp rob.jar:jruby.jar ruby.rob
Web Start
在我們繼續編寫JNLP文件前,我們需要對Jars進行簽名。很不幸需要這麼做,因為JRuby使用了反射,這樣就需要更多的許可,你可以參閱JRuby Wiki來了解更多的細節。最簡單的方法就是使用JDK自帶的keytool來創建一個測試證書。
keytool -genkey -keystore myKeystore -alias myself
keytool -selfcert -alias myself -keystore myKeystore
從現在開始,每次你修改Jars時,必須要更新簽名,否則在運行時你就會得到一個SecurityException。
jarsigner -keystore myKeystore jruby.jar myself
jarsigner -keystore myKeystore rob.jar myself
既然我們已經准備好Jars了,我們就來看一下JNLP文件。下面提供了對應用的一個最小配置。一些字段是規范要求的,比如title、vendor、j2se標簽以及security段。jar標簽代表了Jars的最終位置。它還可以使用file://形式的URL指向本地文件,在開發中這會很方便。ObjectSpace屬性也需要在此設定,這可以通過property標簽來完成。
Mirko Stocker
如果你忽略了AOT段或者僅僅想使用單個jar的方式,那麼你就必須修改jnlp文件並包含-e參數,那麼你的application-desc看起來應該像這樣:
[...]
-r
rob
[...]
最後一步是將Jars和JNLP文件上傳到指定位置。現在你應該可以在浏覽器或者使用shell中的javaws工具來打開鏈接了。
問題解決
為了讓你的浏覽器能通過Web Start加載應用,我們需要使用application/x-java-jnlp-file MIME類型來發布JNLP文件。因此如果你的浏覽器僅僅顯示了JNLP文件的內容並且javaws沒有自動加載的話,你就需要改變web服務器的配置。例如,Apache需要在mime.types中增加如下指令:
application/x-java-jnlp-file jnlp