1. 引言
2004年Ruby on Rails的横空出世让大家为之一惊,很多Java社区对它也投去关注的目光,现在RoR已经渐渐为人接受,被运用于不少实际项目之中,这也让本来不怎么 吸引眼球的Ruby从角落里走了出来。不少开发者在试用了Ruby和RoR后产生了浓厚的兴趣,毕竟Ruby的语法是如此的有趣,Rails中的开发是如 此的便捷,有时它替你安排好了一切,敲键盘就是了。
但Ruby毕竟和主流的Java/.Net还存在一定距离,比如开发者数量,受关注度等等。更 关键的是它缺乏像Java那样的库支持,很多时候不得不自己动手“丰衣足食”。后来人们想到了要去跨越语言的边界,但做总是比想要难,好在出现了 JRuby,在它的帮助下,这条边界已经不再不可逾越,所以勇敢地跨出第一步吧!
2. JRuby的Java集成
如何让Ruby与Java紧密地结合在一起呢?你可以在Ruby中引用Java类、Java原子类型、Java数组,实现Java接口,继承Java类;也可以在Java中使用Ruby的代码。其实一切都很简单,你需要的只是一点小小的魔法而已。
2.1. JRuby中调用Java
在接触JRuby前我使用过RJB(Ruby Java Bridge,http://rjb.rubyforge.org/),两者都提供在Ruby中调用Java的功能,仅在这点上来说,感觉它们差不多,其实JRuby的功能要强大的多。如果你只是想在Ruby中简单地调用一些Java代码,那可以考虑RJB。
要在JRuby中使用Java,先要声明程序中需要Java集成,有两种方法,一种用require 'java';另一种直接使用java::java.util.ArrayList这样的语法。无论是何种方法,都要保证所用的Java类在CLASSPATH中。
- require 'java'
- java::java.util.ArrayList
require 'java' java::java.util.ArrayList
Java类的使用也有几种选择:
- 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"} 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"
你可以这样来调用System.out.println:
- java.lang.System.out.println("Hello, world")
java.lang.System.out.println("Hello, world")
值得一提的是这里的”Hello, world”是Ruby的字符串,而非java.lang.String,JRuby会自动对一些类型进行转换,开发者无需自己动手。
在Java 中,方法和变量都用fooBar这样的形式,而Ruby中则是foo_bar,显然在代码中同时出现这两种形式会很不协调,JRuby很聪明地将Java 中的fooBar转为了foo_bar,而常见的getter和setter,也简化为了成员属性的名称,foo是getFoo(),而foo=是 setFoo()。
用to_java()能将一个Ruby的数组转换为Java中的Object[],如果想要指定数组的类型可以这样:
- [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)。
如要直接创建并使用Java数组,像下面这样就行了:
- 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" d[0][1] = "World"
2.2. 扩展Java
对Java的扩展主要是用Ruby来实现接口和继承类。先来看下如何实现接口:
- 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 end
如果要实现多个接口,import就可以了,对于未实现的方法,JRuby会把它交给method_missing。有一点要注意,compareTo在这里不能写成compare_to。
至于继承Java类就更容易了,几乎和继承Ruby类没什么区别:
- class MyStringBuffer < java.lang.StringBuffer
- def append(v)
- end
- end
class MyStringBuffer < java.lang.StringBuffer def append(v) end end
StringBuffer类的append方法有多个overload的版本,接收多个不同类型的参数,它们都会被统一到这个唯一的方法上,理由嘛很好理解,不是吗?
除 此之外,JRuby还为Java的集合类提供了很多扩展,让你能用Ruby的方式来操作Java集合。比方说,java.util.Map多了each方 法、 []方法和[]=方法;java.lang.Comparable拥有了<=>方法;所有继承自java.util.Collection的 类有了each、<<、+、-和length方法;java.util.List有了[]和[]=方法,还实现了sort和sort!方法。
2.3. Java中调用JRuby
Java 6中加入了JSR223,让Java可以支持脚本语言,如果你的运气没这么好,还停留在Java 5或者Java 1.4上,那可以考虑用BSF或者是直接用JRuby Runtime。当然,除非情况特殊,否则不推荐使用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 "); } }
3. JRuby on Rails项目的部署
既 然是RoR的项目,自然是可以借鉴已有的最佳实践,JavaEye上对此已有很多讨论。不过目前还不能在JRuby on Rails中使用FastCGI,所以像JavaEye用的LightTPD+FastCGI就只能被暂时忽略了,等到FastCGI什么时候被 JRuby on Rails支持了再让它重见天日吧。
既然用JRuby而非Ruby,那自然是有一定原因的,不是想在系统中使用Java资源,就是开发者有浓厚的Java情结。既然是JRuby on Rails,就让我们来看下Java开发者会比较喜欢的部署方式。
3.1. Java EE Web容器中的部署
如果RoR的项目跑在Tomcat里,那会是种什么感觉?如果Ruby文件全变成class了又会怎么样?这可不是睁眼说瞎话,在JRuby on Rails里你就能这么做!
GoldSpike能够把整个Rails应用程序打包为一个War文件,有了War就能在任意Java EE Web容器中进行直接的部署。
以插件形式安装好GoldSpike后,可以在Rails项目中用它提供的Rake任务来生成War文件。GoldSpike的配置都做在config/war.rb文件中,用如下命令开始构建,运行后会生成一个与项目同名的War文件:
- jruby -S rake war:standalone:create
jruby -S rake war:standalone:create
下面来介绍些war.rb配置时的DSL:
servlet CLASSNAME 分派Rails请求的类,默认是org.jruby.webapp.RailsServlet。
compile_ruby BOOLEAN 打包前编译所有的Ruby文件,目前这个功能似乎还不是很理想,所以默认是false。记得Robbin以前曾发过一篇文章说突然发现XRuby做的事情 很有前途,JRuby同样能够做到,其实我不在乎用什么,只要把我的Ruby代码编程字节码就行。
add_gem NAME, VERSION 你需要手动添加程序用到的Gem包,好在有add_gem_dependencies,把它设为true(默认就是true),GoldSpike会自动添加依赖的包的。
- add_gem 'RedCloth', '= 3.0.4'
add_gem 'RedCloth', '= 3.0.4'
datasource_jndi BOOLEAN 如果在程序中使用了JNDI提供数据源,那将这个参数设置为true,并用datasource_jndi_name NAME来提供JNDI名称,JRuby on Rails中可以用ActiveRecord-JDBC来访问数据库,其中能够使用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'
GoldSpike是JRuby-extras的一部分,欲了解相关信息,请访问https://rubyforge.org/projects/jruby-extras/ ,其中还有ActiveRecord-JDBC等信息。此外,由Nick Sieger开发的Warbler也是一个不错的选择。
3.2. Mongrel集群
在RoR 中常会启动一组Mongrel来处理请求,JRuby on Rails中同样可以这么做,只是在做法上有所不同,因为直接启动几个Mongrel实例的同时会起好几个JVM,启动速度慢不说,还很耗资源,所以 JRuby提供了一种机制,在同一个JVM中启动几个JRuby Runtime来运行程序。我们可以利用这种机制在一个JVM中启动几个Mongrel监听连续端口。这里会用到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::stop
第一条命令会创建一个配置文件,启动4个Mongrel实例,端口从4000开始,JRuby通过20202端口来监听发送的命令(JRuby自己起了个服务器,接收命令,在一个JVM里运行),-K是服务器用的密钥。
至于放在Mongrel前处理静态资源及负载均衡的服务器,请自行查阅网上其他资源。
4. 总结
本文简单地介绍了JRuby中Ruby与Java的互操作和JRuby on Rails项目的部署问题,希望能够起到一个抛砖引玉的作用,让大家更多地关注JRuby,虽然它还有这样那样的问题,但也不失为一个好的选择。
JRuby 目前的最大目标是与Ruby兼容,所以大量的精力都放在处理兼容性上,相信以后在性能上会有所提高,其实JRuby的速度已经慢慢接近C Ruby了。再者,JRuby背后有Sun的支持,NetBeans IDE 6.0中默认带了JRuby支持,GlassFish V3也将对它有所支持,这不都暗示JRuby将有所作为吗?