2008-01-24

当Ruby遇到Java,勇敢地跨越边界吧

关键字: jruby rails

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

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"

如果类是在java、javax、org或者com包中的,那还可以直接引用它们。

JString = java.lang.String
y = JString.new "Hello, world"

你可以这样来调用System.out.println:

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"}

常用的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"

2.2. 扩展Java
对Java的扩展主要是用Ruby来实现接口和继承类。先来看下如何实现接口:

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

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 ");
  }
}

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

下面来介绍些war.rb配置时的DSL:

写道
exclude_files FILENAME 用来指定不想被放入War的文件,可以使用通配符。
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' 

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'  

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

第一条命令会创建一个配置文件,启动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将有所作为吗?

评论
iMzw 2008-02-25
大家可以看看这篇文章。
http://docs.google.com/Doc?id=dg679957_10fn3fhmcn

我当时Reseach JRuby call Java,准备使用JRuby On Rails。最终转向了Groovy on Grails。我的原因(仅仅是个人原因)
1.性能
2.Rails无法很好的Tomcat中运行。
3.RubyAMF 功能不是很强(我要用AMF,而BlazeDS很好(Java))
4.JRuby与Java互操作很繁琐,而且有很多兼容性问题。
mathgl 2008-02-21
只有适合与否的语言吧。语言的流行也要看一点点运气的
pig345 2008-02-19
robbin 写道
pig345 写道
诚恳地问一个重要的问题:使用C编写的第三方Ruby库能在JRuby里面使用么???

不能,但是如果你用JRuby的话,Java肯定会有相应版本的库。比方说ferret,你可以用Java的lucene,而且可能更好,比方说Rmagick,你可以用Java 2D等等。

不过也有一些C库,提供了jruby版本的gem,比方说hpricot。


谢了robbin,这样在Ruby和JRuby之间平滑移植,是不大可能了,比较麻烦。
ningshuihan 2008-02-18
不要重复你自己
惯例优于配置

光这两条原则就挺诱惑人的,呵呵
siuying 2008-02-07
DigitalSonic 写道
Ruby的确是有些年头了,和Java差不多,但在Rails出现前普及度不高,甚至都没什么人知道。虽然Ruby可以做的东西很多(我用Ruby在Freebsd下做过流量监控限制的系统),但大多数人在讲到Ruby时都会把它和Rails联系起来,和Web开发联系起来,它在这方面的确不错,这是有目共睹的。

个人认为一般情况下语言本身并没有什么特别的好坏之分,拿JavaScript举个例子,在Web 2.0热起来前,多数人只是用JavaScript做点小特效,搞点表单验证,少有大的应用,但现在呢?它到底算好还是不好?有时真的只是机遇问题而已。

没有人说Ruby是银弹,Ruby和JRuby只是给你多一个选择而已,多条路又不是坏事,至于用或不用根据实际情况自己判断就好了。


語言有清楚的好壞之分。 試問如果 Java 跟 C 一樣,為甚麼今天的中間件都是用 Java 而不是用 C 呢?或者為甚麼人們用 C/C++ 寫 OS 不再用 FORTRAN呢?

一些在 Java 一定要攪 AOP 才有的特性,在 Ruby 可以輕易做到。Closure 很美妙,讓那些無用的 inner class 都不用了。還有因為動態語言的關系讓編程精簡許多,看一個標準的 Active Records 的類比同樣的 Hibernate 的類簡短優雅得多。。。 當然要是以現在的掙錢能力來說, Java 還是最好的,但我會同時放眼留意著最好的新平台。
siuying 2008-02-07
ken1984 写道
鸡肋,新瓶装旧酒,就像PHP也可以引用JAVA一样。


不同。JRuby on Rails 可以讓 Rails 的軟件直接 deploy 進 Java Container ,並使用 Java 的 Library。

是說服老闆轉用 Rails 的好方法... "這是在 Java Container 上運行的系統"
庄表伟 2008-02-04
xiaolin0105 写道
sonicluo3 写道
现在java的最主要的创新是JVM而非java语法本身,让各种编程语言跑在同一个平台(JVM)里面是一件非常美妙的事情.

你是说.net的clr? 我不知道jvm还可以跑java以外的编程语言啊?除非把其他语言都编译成java bytecode...


Programming Languages for the Java Virtual Machine
xiaolin0105 2008-02-04
sonicluo3 写道
现在java的最主要的创新是JVM而非java语法本身,让各种编程语言跑在同一个平台(JVM)里面是一件非常美妙的事情.

你是说.net的clr? 我不知道jvm还可以跑java以外的编程语言啊?除非把其他语言都编译成java bytecode...
robbin 2008-01-30
pig345 写道
诚恳地问一个重要的问题:使用C编写的第三方Ruby库能在JRuby里面使用么???

不能,但是如果你用JRuby的话,Java肯定会有相应版本的库。比方说ferret,你可以用Java的lucene,而且可能更好,比方说Rmagick,你可以用Java 2D等等。

不过也有一些C库,提供了jruby版本的gem,比方说hpricot。
pig345 2008-01-30
诚恳地问一个重要的问题:使用C编写的第三方Ruby库能在JRuby里面使用么???
DigitalSonic 2008-01-29
Ruby的确是有些年头了,和Java差不多,但在Rails出现前普及度不高,甚至都没什么人知道。虽然Ruby可以做的东西很多(我用Ruby在Freebsd下做过流量监控限制的系统),但大多数人在讲到Ruby时都会把它和Rails联系起来,和Web开发联系起来,它在这方面的确不错,这是有目共睹的。

个人认为一般情况下语言本身并没有什么特别的好坏之分,拿JavaScript举个例子,在Web 2.0热起来前,多数人只是用JavaScript做点小特效,搞点表单验证,少有大的应用,但现在呢?它到底算好还是不好?有时真的只是机遇问题而已。

没有人说Ruby是银弹,Ruby和JRuby只是给你多一个选择而已,多条路又不是坏事,至于用或不用根据实际情况自己判断就好了。
spring5365 2008-01-29
都说ruby怎么怎么好,但问个问题为什么ruby93年出来,java 95年出来的,但现在java成熟的技术和实现的项目要比ruby多很多呢?还是实践能证明一切吧
庄表伟 2008-01-28
aninfeel 写道
对这种炒作的东西已经心灰意冷了

人家好好的写一篇技术文章,怎么就成炒作了?
DigitalSonic 2008-01-28
ken1984 写道
鸡肋,新瓶装旧酒,就像PHP也可以引用JAVA一样。

也不能这么说,两者还是有区别的,JRuby并非为了在Ruby中引用Java才搞的。
引用
JRuby is a 100% Java implementation of the Ruby programming language. It is Ruby for the JVM.

JRuby provides a complete set of core "builtin" classes and syntax for the Ruby language, as well as most of the Ruby Standard Libraries. The standard libraries are mostly Ruby's own complement of .rb files, but a few that depend on C language-based extensions have been reimplemented. Some are still missing, but we hope to implement as many as is feasible.
Godlikeme 2008-01-27
兼容带来的后果就是一团糟,发展路线不会很清晰,Jmess.
还是早日有个了断吧。
zzsczz 2008-01-27
hibernate vs ejb,jdo jruby vs groovy 本来没什么标准的,用的人多了就是事实标准 有的软件是根据需要进化而来,而有的是认为捏造的。。
ken1984 2008-01-26
鸡肋,新瓶装旧酒,就像PHP也可以引用JAVA一样。
liuzongan 2008-01-26
好,谢谢您,写得不错啊
sonicluo3 2008-01-25
现在java的最主要的创新是JVM而非java语法本身,让各种编程语言跑在同一个平台(JVM)里面是一件非常美妙的事情.
DigitalSonic 2008-01-25
neusun 写道
对 Jruby性能问题似乎遇到了瓶颈

正如我前面说的,JRuby首要关注的是兼容性,不过JRuby团队也在不断改善JRuby的性能。1月8号发布了JRuby 1.1RC1,介绍里有这么一段话:
引用
JRuby 1.1RC1 is the first release candidate of JRuby 1.1. JRuby 1.1 represents a concerted focus on speed and refinement. Ruby code can completely compile in an Ahead Of Time (AOT) or Just In Time (JIT) mode; yielding a faster Ruby! It also uses less memory than our previous releases.

此外,JRuby可以把rb编译为class,官方站点中有篇关于JRuby Compiler的wiki,其中有一节是性能对比,内容如下:(估计用的还是JRuby 0.9.9)
引用
A few microbenchmarks comparing Ruby, JRuby interpreted, and JRuby compiled (server VM numbers show worst and best numbers):
fib(30) Ruby: 1.67s
fib(30) JRuby interp (client VM): 3.93s
fib(30) JRuby interp (server VM): 2.28s to 2.08s
fib(30) JRuby compiled (client VM): 1.89s to 1.79s
fib(30) JRuby compiled (server VM): 1.66s to 0.86s
发表评论

提醒: 该博客已发表在公共论坛,博客所有留言会成为论坛回贴,留言请注意遵守论坛发贴规则

您还没有登录,请登录后发表评论