1. 引言
2004年Ruby on Rails的橫空出世讓大家為之一驚,很多Java社區(qū)對它也投去關(guān)注的目光,現(xiàn)在RoR已經(jīng)漸漸為人接受,被運用于不少實際項目之中,這也讓本來不怎么吸引眼球的Ruby從角落里走了出來。不少開發(fā)者在試用了Ruby和RoR后產(chǎn)生了濃厚的興趣,畢竟Ruby的語法是如此的有趣,Rails中的開發(fā)是如此的便捷,有時它替你安排好了一切,敲鍵盤就是了。
但Ruby畢竟和主流的Java/.Net還存在一定距離,比如開發(fā)者數(shù)量,受關(guān)注度等等。更關(guān)鍵的是它缺乏像Java那樣的庫支持,很多時候不得不自己動手“豐衣足食”。后來人們想到了要去跨越語言的邊界,但做總是比想要難,好在出現(xiàn)了JRuby,在它的幫助下,這條邊界已經(jīng)不再不可逾越,所以勇敢地跨出第一步吧!
2. JRuby的Java集成
如何讓Ruby與Java緊密地結(jié)合在一起呢?你可以在Ruby中引用Java類、Java原子類型、Java數(shù)組,實現(xiàn)Java接口,繼承Java類;也可以在Java中使用Ruby的代碼。其實一切都很簡單,你需要的只是一點小小的魔法而已。
2.1. JRuby中調(diào)用Java
在接觸JRuby前我使用過RJB(Ruby Java Bridge,http://rjb.rubyforge.org/),兩者都提供在Ruby中調(diào)用Java的功能,僅在這點上來說,感覺它們差不多,其實JRuby的功能要強大的多。如果你只是想在Ruby中簡單地調(diào)用一些Java代碼,那可以考慮RJB。
要在JRuby中使用Java,先要聲明程序中需要Java集成,有兩種方法,一種用require 'java';另一種直接使用java::java.util.ArrayList這樣的語法。無論是何種方法,都要保證所用的Java類在CLASSPATH中。
- require 'java'
-
- java::java.util.ArrayList
-
- require 'java'
require 'java'java::java.util.ArrayListrequire 'java'
java::java.util.ArrayListJava類的使用也有幾種選擇:
- include_class "java.util.HashMap"
- x = HashMap.new
- x.put("foo","bar")
-
- include_class("java.lang.String") {|pkg,name| "JString"}
- y = JString.new "Hello, world"
-
- include_class "java.util.HashMap"
- x = HashMap.new
- x.put("foo","bar")
-
- include_class("java.lang.String") {|pkg,name| "JString"}
include_class "java.util.HashMap"x = HashMap.newx.put("foo","bar")include_class("java.lang.String") {|pkg,name| "JString"}y = JString.new "Hello, world"include_class "java.util.HashMap"x = HashMap.newx.put("foo","bar")include_class("java.lang.String") {|pkg,name| "JString"}
y = JString.new "Hello, world"如果類是在java、javax、org或者com包中的,那還可以直接引用它們。
JString = java.lang.String
y = JString.new "Hello, world"
JString = java.lang.String
y = JString.new "Hello, world"你可以這樣來調(diào)用System.out.println:
java.lang.System.out.println("Hello, world")
java.lang.System.out.println("Hello, world")值得一提的是這里的”Hello, world”是Ruby的字符串,而非java.lang.String,JRuby會自動對一些類型進行轉(zhuǎn)換,開發(fā)者無需自己動手。
在Java中,方法和變量都用fooBar這樣的形式,而Ruby中則是foo_bar,顯然在代碼中同時出現(xiàn)這兩種形式會很不協(xié)調(diào),JRuby很聰明地將Java中的fooBar轉(zhuǎn)為了foo_bar,而常見的getter和setter,也簡化為了成員屬性的名稱,foo是getFoo(),而foo=是setFoo()。
用to_java()能將一個Ruby的數(shù)組轉(zhuǎn)換為Java中的Object[],如果想要指定數(shù)組的類型可以這樣:
- [1,2,3].to_java :float
- ["str", "str2"].to_java java.lang.String
-
- [1,2,3].to_java :float
- ["str", "str2"].to_java java.lang.String
[1,2,3].to_java :float # new float[] {1,2,3}["str", "str2"].to_java java.lang.String # new String[]{"str","str2"}[1,2,3].to_java :float # new float[] {1,2,3}["str", "str2"].to_java java.lang.String # new String[]{"str","str2"}
常用的symbol有以下幾種::boolean、:byte、:char、:double、:float、:int、:long、:short、:object、:string、:big_decimal(:decimal)和:big_integer(:big_int)。
如要直接創(chuàng)建并使用Java數(shù)組,像下面這樣就行了:
- java.lang.String[3].new
- java.lang.String[].new [3,3]
- java.lang.String[3][3].new
-
- d = java.lang.String[3,3].new
- d[0][0] = "Hello"
- d[0][1] = "World"
-
- java.lang.String[3].new
- java.lang.String[].new [3,3]
- java.lang.String[3][3].new
-
- d = java.lang.String[3,3].new
- d[0][0] = "Hello"
java.lang.String[3].newjava.lang.String[].new [3,3]java.lang.String[3][3].newd = java.lang.String[3,3].newd[0][0] = "Hello"d[0][1] = "World"java.lang.String[3].newjava.lang.String[].new [3,3]java.lang.String[3][3].newd = java.lang.String[3,3].newd[0][0] = "Hello"
d[0][1] = "World"2.2. 擴展Java
對Java的擴展主要是用Ruby來實現(xiàn)接口和繼承類。先來看下如何實現(xiàn)接口:
- class Compare
- import java.lang.Comparable
-
- def compareTo o
- this <=> o
- end
-
- end
-
- class Compare
- import java.lang.Comparable
-
- def compareTo o
- this <=> o
- end
class Compareimport java.lang.Comparabledef compareTo othis <=> oendendclass Compareimport java.lang.Comparabledef compareTo othis <=> oend
end如果要實現(xiàn)多個接口,import就可以了,對于未實現(xiàn)的方法,JRuby會把它交給method_missing。有一點要注意,compareTo在這里不能寫成compare_to。
至于繼承Java類就更容易了,幾乎和繼承Ruby類沒什么區(qū)別:
- class MyStringBuffer < java.lang.StringBuffer
- def append(v)
- end
- end
-
- class MyStringBuffer < java.lang.StringBuffer
- def append(v)
- end
class MyStringBuffer < java.lang.StringBufferdef append(v)endendclass MyStringBuffer < java.lang.StringBufferdef append(v)end
endStringBuffer類的append方法有多個overload的版本,接收多個不同類型的參數(shù),它們都會被統(tǒng)一到這個唯一的方法上,理由嘛很好理解,不是嗎?
除此之外,JRuby還為Java的集合類提供了很多擴展,讓你能用Ruby的方式來操作Java集合。比方說,java.util.Map多了each方法、 []方法和[]=方法;java.lang.Comparable擁有了<=>方法;所有繼承自java.util.Collection的類有了each、<<、+、-和length方法;java.util.List有了[]和[]=方法,還實現(xiàn)了sort和sort!方法。
2.3. Java中調(diào)用JRuby
Java 6中加入了JSR223,讓Java可以支持腳本語言,如果你的運氣沒這么好,還停留在Java 5或者Java 1.4上,那可以考慮用BSF或者是直接用JRuby Runtime。當(dāng)然,除非情況特殊,否則不推薦使用Runtime。
JSR223和BSF的用法比較相近,所以這里只演示一下JSR223。先去下載一個JSR223引擎包,把其中的JRuby引擎放進CLASSPATH。代碼如下:
- import javax.script.ScriptEngine;
- import javax.script.ScriptEngineManager;
-
- public class JRubyJSR223 {
- public static void main(String[] args) throws Exception {
- ScriptEngineManager m = new ScriptEngineManager();
- ScriptEngine rubyEngine = m.getEngineByName("jruby");
- rubyEngine.getContext().setAttribute("num", new Integer(4), ScriptContext.ENGINE_SCOPE);
- rubyEngine.eval("puts 2 + $num ");
- }
- }
-
- import javax.script.ScriptEngine;
- import javax.script.ScriptEngineManager;
-
- public class JRubyJSR223 {
- public static void main(String[] args) throws Exception {
- ScriptEngineManager m = new ScriptEngineManager();
- ScriptEngine rubyEngine = m.getEngineByName("jruby");
- rubyEngine.getContext().setAttribute("num", new Integer(4), ScriptContext.ENGINE_SCOPE);
- rubyEngine.eval("puts 2 + $num ");
- }
import javax.script.ScriptEngine;import javax.script.ScriptEngineManager;public class JRubyJSR223 {public static void main(String[] args) throws Exception {ScriptEngineManager m = new ScriptEngineManager();ScriptEngine rubyEngine = m.getEngineByName("jruby");rubyEngine.getContext().setAttribute("num", new Integer(4), ScriptContext.ENGINE_SCOPE);rubyEngine.eval("puts 2 + $num ");}}import javax.script.ScriptEngine;import javax.script.ScriptEngineManager;public class JRubyJSR223 {public static void main(String[] args) throws Exception {ScriptEngineManager m = new ScriptEngineManager();ScriptEngine rubyEngine = m.getEngineByName("jruby");rubyEngine.getContext().setAttribute("num", new Integer(4), ScriptContext.ENGINE_SCOPE);rubyEngine.eval("puts 2 + $num ");}
}3. JRuby on Rails項目的部署
既然是RoR的項目,自然是可以借鑒已有的最佳實踐,JavaEye上對此已有很多討論。不過目前還不能在JRuby on Rails中使用FastCGI,所以像JavaEye用的LightTPD+FastCGI就只能被暫時忽略了,等到FastCGI什么時候被JRuby on Rails支持了再讓它重見天日吧。
既然用JRuby而非Ruby,那自然是有一定原因的,不是想在系統(tǒng)中使用Java資源,就是開發(fā)者有濃厚的Java情結(jié)。既然是JRuby on Rails,就讓我們來看下Java開發(fā)者會比較喜歡的部署方式。
3.1. Java EE Web容器中的部署
如果RoR的項目跑在Tomcat里,那會是種什么感覺?如果Ruby文件全變成class了又會怎么樣?這可不是睜眼說瞎話,在JRuby on Rails里你就能這么做!
GoldSpike能夠把整個Rails應(yīng)用程序打包為一個War文件,有了War就能在任意Java EE Web容器中進行直接的部署。
以插件形式安裝好GoldSpike后,可以在Rails項目中用它提供的Rake任務(wù)來生成War文件。GoldSpike的配置都做在config/war.rb文件中,用如下命令開始構(gòu)建,運行后會生成一個與項目同名的War文件:
- jruby -S rake war:standalone:create
jruby -S rake war:standalone:create
jruby -S rake war:standalone:create下面來介紹些war.rb配置時的DSL:
寫道
exclude_files FILENAME 用來指定不想被放入War的文件,可以使用通配符。
servlet CLASSNAME 分派Rails請求的類,默認(rèn)是org.jruby.webapp.RailsServlet。
compile_ruby BOOLEAN 打包前編譯所有的Ruby文件,目前這個功能似乎還不是很理想,所以默認(rèn)是false。記得Robbin以前曾發(fā)過一篇文章說突然發(fā)現(xiàn)XRuby做的事情很有前途,JRuby同樣能夠做到,其實我不在乎用什么,只要把我的Ruby代碼編程字節(jié)碼就行。
add_gem NAME, VERSION 你需要手動添加程序用到的Gem包,好在有add_gem_dependencies,把它設(shè)為true(默認(rèn)就是true),GoldSpike會自動添加依賴的包的。
- add_gem 'RedCloth', '= 3.0.4'
-
- add_gem 'RedCloth', '= 3.0.4'
add_gem 'RedCloth', '= 3.0.4'add_gem 'RedCloth', '= 3.0.4'
datasource_jndi BOOLEAN 如果在程序中使用了JNDI提供數(shù)據(jù)源,那將這個參數(shù)設(shè)置為true,并用datasource_jndi_name NAME來提供JNDI名稱,JRuby on Rails中可以用ActiveRecord-JDBC來訪問數(shù)據(jù)庫,其中能夠使用JNDI。
maven_library GROUP, NAME, VERSION 項目中如果需要Jar庫,GoldSpike可以直接從Maven庫中下載文件。
- maven_library 'mysql', 'mysql-connector-java', '5.0.4'
maven_library 'mysql', 'mysql-connector-java', '5.0.4'
maven_library 'mysql', 'mysql-connector-java', '5.0.4' GoldSpike是JRuby-extras的一部分,欲了解相關(guān)信息,請訪問https://rubyforge.org/projects/jruby-extras/ ,其中還有ActiveRecord-JDBC等信息。此外,由Nick Sieger開發(fā)的Warbler也是一個不錯的選擇。
3.2. Mongrel集群
在RoR中常會啟動一組Mongrel來處理請求,JRuby on Rails中同樣可以這么做,只是在做法上有所不同,因為直接啟動幾個Mongrel實例的同時會起好幾個JVM,啟動速度慢不說,還很耗資源,所以JRuby提供了一種機制,在同一個JVM中啟動幾個JRuby Runtime來運行程序。我們可以利用這種機制在一個JVM中啟動幾個Mongrel監(jiān)聽連續(xù)端口。這里會用到mongrel_jcluster,建議在用Gem安裝Mongrel時就一起把這個mongrel_jcluster裝了,你總是會用到的。
配置、啟動及停止的命令如下:
- jruby -S mongrel_rails jcluster::configure -e production -p 4000 -c . -N 4 -R 20202 -K yourVerySecretKey
- jruby -S mongrel_rails jcluster::start
- jruby -S mongrel_rails jcluster::stop
-
- jruby -S mongrel_rails jcluster::configure -e production -p 4000 -c . -N 4 -R 20202 -K yourVerySecretKey
- jruby -S mongrel_rails jcluster::start
jruby -S mongrel_rails jcluster::configure -e production -p 4000 -c . -N 4 -R 20202 -K yourVerySecretKeyjruby -S mongrel_rails jcluster::startjruby -S mongrel_rails jcluster::stopjruby -S mongrel_rails jcluster::configure -e production -p 4000 -c . -N 4 -R 20202 -K yourVerySecretKeyjruby -S mongrel_rails jcluster::start
jruby -S mongrel_rails jcluster::stop第一條命令會創(chuàng)建一個配置文件,啟動4個Mongrel實例,端口從4000開始,JRuby通過20202端口來監(jiān)聽發(fā)送的命令(JRuby自己起了個服務(wù)器,接收命令,在一個JVM里運行),-K是服務(wù)器用的密鑰。
至于放在Mongrel前處理靜態(tài)資源及負(fù)載均衡的服務(wù)器,請自行查閱網(wǎng)上其他資源。
4. 總結(jié)
本文簡單地介紹了JRuby中Ruby與Java的互操作和JRuby on Rails項目的部署問題,希望能夠起到一個拋磚引玉的作用,讓大家更多地關(guān)注JRuby,雖然它還有這樣那樣的問題,但也不失為一個好的選擇。
JRuby目前的最大目標(biāo)是與Ruby兼容,所以大量的精力都放在處理兼容性上,相信以后在性能上會有所提高,其實JRuby的速度已經(jīng)慢慢接近C Ruby了。再者,JRuby背后有Sun的支持,NetBeans IDE 6.0中默認(rèn)帶了JRuby支持,GlassFish V3也將對它有所支持,這不都暗示JRuby將有所作為嗎?