随着ruby,特别是ruby on rail在应用领域所取得的成功,近两年来动态语言成为炙手可热的技术之一。由于动态语言灵活、松散的结构,使得实现某些功能特别方便。spring 顺应时势,在spring 2.0中适时推出了对动态语言的支持,允许你以普通bean的方式调用动态语言实现的程序。
阅读导航 |
动态语言介绍 动态语言也称为脚本语言,是介于标签语言(如html,xslt,vml)和静态语言(如c++、c#、java,也称编译语言)之间的语言。 |
动态语言的特性及适用场合 动态语言最突出的特性是语法简洁灵活,易于学习,拥有大量高级程序库,无须从底层开始搭建程序,降低了应用实现的难度。 |
ruby快速入门 ruby无疑是受到关注最多的动态语言, jruby是用java代码实现的ruby解释器,是ruby到java的字节码编译器。 |
spring对动态语言的支持 spring为定义基于动态语言实现的bean提供了专门的schema标签,它对应一个专门的命名空间。 |
其它支持功能 除此以外,spring还提供了两个引人注目的支持,那就是动态刷新和内联脚本。 |
小结
动态语言在经历过几年的沉寂后又开始重放异彩,各种类型的动态语言不断涌现。其中jruby、groovy和beanshell都采用特殊的java编译器进行工作,它们最终可以被编译成标准的java字节码。spring目前对以上三种动态语言提供了支持,你不但可以在spring配置文件定义基于java实现的bean,还可以定义基于动态语言的bean。它们的最终表现并没有什么区别,底层的实现对于调用者是透明的。
虽然spring支持动态语言编写的bean,但是到目前为止,我们并没有看到多少使用动态语言bean的应用案例,应该说大多的介绍停留在技术层面,有待于继续探索动态语言集成spring应用的具体实践。所以除非你确定动态语言bean能给你的应用带来明显的好处,否则对这一实现技术应保持谨慎态度。 动态语言介绍
动态语言也称为脚本语言,是介于标签语言(如html,xslt,vml)和静态语言(如c++、c#、java,也称编译语言)之间的语言。javascript、php、perl、ruby等都是动态语言。动态语言无需编译,它由解释器动态解释执行,一般来说,动态语言拥有比静态语言更大的灵活性和表达能力。
动态语言优势在于灵活,易于开发和学习,劣势在于性能较低。在高性能服务器和并行处理的实现方案里,动态语言的优势掩盖自身的不足。一般来说,完成同样逻辑功能的程序,使用ruby或python之类的动态语言所需的代码量往往只有使用java实现版本的1/10,相比c/c++差距更大。
近两年来,动态语言似乎有山雨欲来风满楼的气势。在各大开发技术网站,有大量介绍动态语言的文章,在各大论坛里,人们针对动态语言的学习、交流、论战、预测的帖子不断成为抢眼热帖,搅动着开发者的神经。
动态语言的特性及适用场合
动态语言最突出的特性是语法简洁灵活,易于学习,拥有大量高级程序库,无须从底层开始搭建程序,降低了应用实现的难度。一个c++程序员,往往经过了几年的训练仍然不能开发出可用的程序,但动态语言的程序员经过几个月的训练就已经可以编写出可复用性的代码了。此外,动态语言的代码往往以开源的形式发布,容易被使用和学习。
程序库往往决定了一种语言应用难度,纵观历史,凡是具有优秀程序库的语言生命力都很顽强,反之则容易销声匿迹。目前几种流行的动态语言,都具有丰富的程序库,在python中,不仅具有诸如网络应用,多线程,图像处理,科学运算之类的程序库,甚至还拥有多套不同量级的framework来辅助web开发。事实上,任何需要的功能几乎都能找到相对稳定的程序库,大部分程序员仅仅需要组合这些库就能编写出强大的应用――这也正是使用动态语言开发程序代码量较少的原因之一。
除了众多的程序库,动态语言本身的种类也远远大于静态语言,同样,每种语言也各具特点。ruby,python,groovy,perl是四种颇具代表性的动态语言,按照一般的看法,这些语言适合用来黏合不同的系统和模块,素有“胶水”的之称。后来人们发现“胶水”不仅仅能粘合其他模块,本身也具有强大的功能。在没有性能问题的重要性不突出的场合,动态语言看起来无所不能了,无论是网站还是应用程序,都能看到动态语言的身影。对时下的程序员来说,掌握一种动态语言已不仅仅是赶时髦,虽然你不一定能够靠单单一种动态语言找到一份喜欢的工作,但适时地采用动态语言,一定可以让你的工作变的简单许多。
系统软件和应用软件的界限已经越来越清晰,开发系统软件仍然需要静态语言,而应用软攀???琅?? ?o??ū件和web网站的开发越来越适合采用动态语言。编写操作系统之类的系统软件对性能要求依然是首位的,而对于应用软件来说,缩短开发周期显然更加重要。
由于大部分的应用软件都采用b/s结构,web框架对于开发语言显得越来越重要,大多数的动态语言也都拥有自己的web框架。python的web模型可谓五花八门,django、turbogear、webware以及quixote是其中的代表者。而对于ruby来说,ruby on rails似乎就是不二选择,目前ruby on rails风光无限,甚至已经有很多web网站构建在ruby on rails之上并取得了良好的效果,着名的javaeye就是其中的代表者。perl的mason名气很大,也很成熟,其实perl本身就很适合做web开发,只要有fastcgi,利用cpan程序库,大部分应用都能轻松解决了。 ruby快速入门
ruby无疑是受到关注最多的动态语言, jruby是用java代码实现的ruby解释器,是ruby到java的字节码编译器。2006年9月,sun收购了jruby,jruby的核心成员charles nutter和thomas enobo也加入了sun公司。从这次收购行为中可以看出ruby 在sun 战略中的地位,ruby 很有可能成为jvm第一个支持的动态语言。
系统学习ruby的语言已经远远超出了本文的范畴,在这一小节里,我们将通过一些零散的小段代码感受一下ruby语言,或许这对于ruby入门有一定的引导作用。
对于初学者来说,我们推荐安装ruby entry package for win32版本的ruby,它是由马康弘先生在cygwin版ruby的基础上添加了安装程序后制作而成的,特别适合初学者安装使用。你可以从http://homepage1.nifty.com/arima/ruby获取安装程序。
在安装完成后,打开dos窗口,输入以下的命令以测试ruby是否安装成功:
| d:/>ruby -v ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32] |
如果安装成功,将如上所示打出ruby的版本号信息。通过开始菜单->程序->ruby-xx-yy->fxri打开ruby的交互式窗口开始体验ruby的魅力:
图 1 fxri界面 |
hello world
通过puts打印出“hello world”:
| irb(main):001:0> puts "hello world" hello world => nil |
puts在ruby中是一个简单的打印输出命令。后面的“=> nil”表示表达式的结果。puts总是返回nil,相当于java的null。
即时计算器
| irb(main):006:0> 10+2 => 12 irb(main):007:0> 3*4 => 12 irb(main):008:0> 3**3 => 27 irb(main):009:0> math.sqrt(16) => 4.0 |
正如你所看到的,你只要输入运算式并回车就可以立即得到运行结果,这相当于计算器的功能。“**”表示进行幂运算,而math.sqrt()表示进行平方运算。math是ruby内建的数学模块。在ruby中,模块将类似的方法聚集在同一个“家族”的名下。因此,math也包括sin()、cos(攀???琅?? ?o??ū)这样的方法。计算器只能打出结果,那么我们如果引用这些结果呢?这需要使用到变量。
将结果赋值给变量
| irb(main):011:0> a = 3*5 => 15 irb(main):012:0> b =2+3 => 5 irb(main):013:0> math.sqrt(a+b) => 4.47213595499958 |
上面的代码定义了两个变量,第三行代码引用变量进行计算。
定义方法
| irb(main):015:0> def greeting_1 irb(main):016:1> puts "how are you!" irb(main):017:1> end => nil |
上面的代码中第1行“def greeting”标志着方法定义的开始。它告诉ruby当前正在定义一个名为greeting的方法。下面一行是方法体:puts "how are you!"。最后,也就是第3行“end”通知ruby已经完成了方法的定义。ruby通过 “=> nil”进行回应,说明已经正确定义了一个方法。
下面的代码定义了一个带参的方法:
| irb(main):021:0> def greeting_2(name) irb(main):022:1> puts "how are you,#{name}" irb(main):023:1> end |
“#{name}”是ruby在某个字符串中引用变量的方法,相当于java的"how are you"+name,你也可以为入参指定一个默认值:
| irb(main):024:0> def greeting_3(name = "john") irb(main):025:1> puts "how are you,#{name}" irb(main):026:1> end => nil |
在定义好方法后,下面我们来调用这些方法:
| irb(main):032:0> greeting_1 how are you! => nil irb(main):034:0> greeting_2("tom") how are you,tom => nil irb(main):035:0> greeting_3 how are you,john => nil |
对于没有入参的方法来说,可以直接不带括号进行调用。对于有默认值的入参可以不提供参数值。
定义类
下面的代码定义了一个具有两个方法的waiter类:
| irb(main):004:0> class waiter irb(main):005:1> def initialize(name="sir") irb(main):006:2> @name = name irb(main):007:2> end irb(main):008:1> def greeting irb(main):009:2>攀???琅?? ?o??ū puts "how are you,#{@name}" irb(main):010:2> end irb(main):011:1> def send_off irb(main):012:2> puts "byebye #{@name}" irb(main):013:2> end irb(main):014:1> end |
@name中的@是新出现的关键字,表示定义一个实例变量,实例变量可以被类中的所有方法引用。
使用类
如何调用刚才定义的waiter,并让它真正工作起来呢?我们马上着手做这样的工作:
| irb(main):015:0> waiter.new("miss") => #<waiter:0xdbd3a14 @name="miss"> irb(main):016:0> w = waiter.new("miss") => #<waiter:0xdbd0d8c @name="miss"> irb(main):017:0> w.greeting how are you,miss => nil |
但你不能通过g.name引用实例变量,ruby允许你随时改变类的定义,下面的代码让name实例变量对外可见。
类的动态调整
通过下面的代码,改变waiter类的结构定义,让name实例变量对外可见:
| irb(main):024:0> class waiter irb(main):025:1> attr_accessor :name irb(main):026:1> end => nil irb(main):027:0> w = waiter.new("miss") => #<waiter:0xdbba230 @name="miss"> irb(main):028:0> w.name => "miss" |
在ruby语言中,你能够多次修改某个类,而修改所带来的变化自动对后面的调用生效。我们通过使用attr_accessor使name属性对外可见,相当于在java中将private调整为public。动态语言的灵活性之一表现在程序的过程中可以调整程序本身,甚至在程序中创建另外一段程序并动态进行执行。所以有人说,不管是大师还是一般的开发者使用java编写的程序在结构上都不会有太大的差别,但大师和一般开发者用动态语言编写的代码却可能有着天壤之别。 将类保存为类文件
前面的例子都以交互的方式操作代码,代码不能够在其它类中重用。如果希望多次使用同一个类,则需要将代码保存到文件中。你可以通过开始->程序->ruby-xxx-yyy->scite打开可以编写ruby程序文件的编辑器,如下图所示:
![]() 图 2 scite编辑器 |
下面的例子编写了一个waiter类,并最终将其保存到waiter.rb类文件中:
| # this is a simple example ①使用#进行注释 class waiter attr_accessor :names def initialize(names = "mr.") @names = names end def greeting if @names.nil? ②@names是否为空 puts "..." elsif @names.respond_to?("each"攀???琅?? ?o??ū) ③如果@names对象具有each方法,那么进行迭代 @names.each do |name| ④ puts "how are you! #{name}" end else puts "how are you #{@names}" end end end if __file__ == $0 ⑤ w = waiter.new w.greeting w.names ="mr. john" w.greeting w.names =["mr.john","cliton miss","mr. tomson"] w.greeting end |
④处的each是一种方法,它接受一个代码块,然后针对列表中的每个成员执行这个代码块,而在do和end之间的部分便是类似于java匿名类的代码块。在竖杠之间的变量是代码块的参数name,它作为代码块参数被绑定为列表成员。而代码块puts "how are you! #{name}"将使用这个参数进行输出。
⑤部分的代码是对前面定义类的调用,__file__是一个特殊的变量,它代表了当前文件名。$0是启动程序的文件名。那么代码“if __file__ == $0”意味着检查此文件是否为将被使用的主程序文件。这样做可以使程序文件作为程序库使用,而不是可执行代码;但当此文件被用作执行文件时,也可被执行。 spring对动态语言的支持
spring为定义基于动态语言实现的bean提供了专门的schema标签,它对应一个专门的命名空间。虽然你也可以使用基于factorybean的dtd配置定义动态语言实现的bean,但这将导致冗长的配置,而且没有隐藏spring的底层实现细节,spring强力推荐你采用基于schema的配置实现。
在采用基于schema的动态语言配置之前,你必须在spring配置文件中引入动态语言的命名空间,其配置如下所示:
代码清单 1 引入lang命名空间
| <?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xmlns:lang="http://www.springframework.org/schema/lang" ①引入动态语言命名空间 xsi:schemalocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/lang ②指定schema文件位置 http://www.springframework.org/schema/lang/spring-lang-2.0.xsd"> … </beans> |
spring通过lang命名空间为支持的动态语言提供配置。<lang:jruby>、<lang:groovy>、<lang:bsh>分别对应jruby、groovy和beanshell动态语言。将来spring还势必对其它一些动态语言提供支持,但这一配置结构可以轻松地进行扩展,所以总体结构是稳定的。
spring使用jdk动态代理技术为动态语言类生成java的代理实例,它需要一个java接口的支持。也就是说,使用动态语言对java接口提供实现,而spring将其“翻译”成java接口的代理实例。
下面,我们先定义一个java的接口,在本文后面的内容中,我们将通过jruby对该接口提供实现:
代码清单 2 java接口
| packa攀???琅?? ?o??ūge com.baobaotao.service; public interface bbtforum { string getdesc(); } |
这个接口很简单,我们不去关心这个接口的功能是否适合动态语言实现,仅希望通过该接口讲解要spring中使用动态语言的步骤,一旦你掌握了这些基本步骤后,你就可以很容易地实现一些复杂的应用。
基于jruby的bean
要在spring中使用jruby,必须将jruby.jar和cglib-nodep-2.1_3.jar类包添加到类路径下,你可以在spring完整发布版本中找到它们。
下面的脚本使用jruby语言对bbtforum接口提供实现,该脚本文件命名为rubybbtforum.rb:
代码清单 3 rubybbtforum.rb:通过jruby对bbtforum提供实现
| require 'java' include_class 'com.baobaotao.service.bbtforum' class rubybbtforum < bbtforum def setdesc(desc) @@desc = desc end def getdesc @@desc; end end |
rubybbtforum.new ①实例化并返回rubybbtforum类实例
注意jruby源码的最后一行的“rubymessenger.new”,如①所示,该语句实例化动态语言定义的类并返回对应类的实例。如果你忘记添加该行代码,并不意味着所有努力白费了, spring将通过反射机制扫描这个jruby脚本文件,找出一个类并实例化之。如果不提供这一行且spring无法在jruby脚本文件中找到可实例化的类,就会抛出scriptcompilationexception异常。
在编写好这个rubybbtforum.rb脚本文件后,将其放到类路径下。现在我们就可以着手在spring中使用这个脚本文件,并在java程序中通过bbtforum接口进行调用了。来看一下具体的配置:
代码清单 4 applicationcontext.xml 配置jruby实现的bean
| … <lang:jruby id="bbtforum1" script-interfaces="com.baobaotao.service.bbtforum" ①脚本所实现的java接口 script-source="classpath:rubybbtforum.rb"> ②jruby脚本文件的地址 <lang:property name="desc" value="this is a baby's forum (jruby) " /> ③注入属性值 </lang:jruby> … |
首先,你必须象代码清单 1一样定义好lang的命名空间,之后你就可以使用<lang:jruby>对jruby的脚本文件进行配置。①处指定了脚本文件实现的java接口,多个接口可以用逗号分隔,spring将jruby脚本动态代理为实现该接口的实例。在②处,通过script-source属性指定jruby脚本文件所在的位置,一般情况下将这些脚本文件放到java的类路径下以便于加载。③处使用<lang:property>为jruby脚本注入一个配置值,你也可以使用ref属性引用spring容器中其它bean。
当在spring中配置好ruby实现的bean中,就可以象java实现的bean一样在配置文件其它地方引用该bean,在程序中调用bean的方法执行操作――对于调用者来说,根本不知道这个bean的具体实现技术。我们通过bbtforumtest测试类中对刚才定义的bbtforum1 bean进行测试:
代码清单 5 bbtforumtest:测试jruby实现的bean
| package com.baobaotao.service; import org.springframework.context.applicationcontext; import org.springframework.context.support.classpathxmlapplicationcontext; import com.baobaotao.service.bbtforum; public class bbtforumtest { public static void main(string[] args) { string config = "applicationcontext.xml"; applicationcontext ctx = new classpathxmlapplicationcontext("applicationcontext.xml"); bbtforum bbtforum = (bbtforum) ctx.getbean("bbtforum1"); ①获取jruby实现的bean system.out.println(bbtforum.getdesc()); ②按正常的方式进行访问 } } |
运行以上代码,将正确输出以下的信息:
this is a baby's forum (jruby)
可见,在spring配置文件中注入的属性值正确应用到jruby实现的bean中,并且可以通过bbtforum接口方法正确调用jruby脚本的功能。 其它支持功能
在上一节中,我们知道spring能够将动态语言编写的脚本“改装”成java语言可以调用的bean。除此以外,spring还提供了两个引人注目的支持,那就是动态刷新和内联脚本。前者允许在不重启spring容器的情况下使脚本文件的变化生效,而后者则允许你在spring配置文件中编写脚本程序。
动态刷新
动态刷新的bean可以监控底层脚本文件的变化,一旦脚本文件内容发生改变就可以自动重新加载,无需重启spring容器。
通过很小的配置动作就可以让基于动态语言的bean拥有动态刷新功能:只要在动态语言对应的配置元素中使用refresh-check-delay属性指定刷新周期的毫秒数就可以了。请看下面例子:
| … <lang:jruby id="bbtforum1" refresh-check-delay="30000" ①指定刷新的时间周期,单位为毫秒 script-interfaces="com.baobaotao.service.bbtforum" script-source="classpath:rubybbtforum.rb"> <lang:property name="desc" value="this is a baby's forum(jruby)" /> </lang:jruby> … |
在①处,我们指定针对bbtforum1 bean的动态刷新周期为30秒。当开发者在外部改变了rubybbtforum.rb脚本文件的内容,spring将动态载入更新的内容。将refresh-check-delay指定为负值(如-1)即可关闭动态刷新的功能。默认情况下,刷新功能就是关闭的。
动态刷新功能对于开发期测试是很有用的,虽然说启动spring容器并不需要花费多少时间,但不重启spring容器就可以使调整生效将可以带来更大的便利。
内联脚本
所谓内联脚本,就是允许在spring配置文件中定义脚本语句,以得到和使用外部脚本文件相同的效果。由于内联脚本直接在spring配置文件中,会导致冗长配置文件,而且在配置文件中编写的脚本文件不能享受代码高亮,诱导输入的好处,所以这个功能一般只适合于进行一些简单的测试。
spring通过<lang:inline-script>元素定义内联脚本,下面的配置将rubybbtforum.rb脚本文件的攀???琅?? ?o??ū内容转移到spring配置文件中:
代码清单 6 applicationcontext.xml:内联脚本
| … <lang:jruby id="bbtforum4" script-interfaces="com.baobaotao.service.bbtforum"> <lang:inline-script> ①内联脚本 <![cdata[ require 'java' include_class 'com.baobaotao.service.bbtforum' class rubybbtforum < bbtforum def setdesc(desc) @@desc = desc end def getdesc @@desc; end end rubybbtforum.new ]]> </lang:inline-script> <lang:property name="desc" value="this is a baby's forum(jruby)" /> </lang:jruby> |
<lang:inline-script>必须在<lang:property>元素之前进行定义,否则会发生错误。由于脚本内容块常常会包括一些xml特殊字符,所以一般情况就需要使用<![cdata[]]>将脚本内容封装起来。
小结
动态语言在经历过几年的沉寂后又开始重放异彩,各种类型的动态语言不断涌现。其中jruby、groovy和beanshell都采用特殊的java编译器进行工作,它们最终可以被编译成标准的java字节码。spring目前对以上三种动态语言提供了支持,你不但可以在spring配置文件定义基于java实现的bean,还可以定义基于动态语言的bean。它们的最终表现并没有什么区别,底层的实现对于调用者是透明的。
虽然spring支持动态语言编写的bean,但是到目前为止,我们并没有看到多少使用动态语言bean的应用案例,应该说大多的介绍停留在技术层面,有待于继续探索动态语言集成spring应用的具体实践。所以除非你确定动态语言bean能给你的应用带来明显的好处,否则对这一实现技术应保持谨慎态度。

闽公网安备 35060202000074号