摘要:
这篇文章,我希望能够和你一起分享我在开发java系统的时候使用ruby的那种兴奋感。我比较一下java和ruby的优点和缺点,而且介绍一下jruby解释器的支持者和反对者...
利用动态脚本编写你的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带来的利益如同功能程序和元程序的概念。