?
本文檔使用 php中文網(wǎng)手冊 發(fā)布
這一節(jié)描述了如何針對Spring所支持的動態(tài)語言定義受Spring所管理的bean。
請注意本章不會解釋這些支持的動態(tài)語言的語法和用法。例如,如果你想在你的某個應(yīng)用中使用Groovy來編寫類,我們假設(shè)你已經(jīng)了解Groovy這門語言。如果你需要了解和動態(tài)語言本身有關(guān)的更多細節(jié),請參考本章末尾第?24.6?節(jié) “更多的資源”一節(jié)。
使用dynamic-language-backed bean要經(jīng)過以下步驟:
編寫針對動態(tài)語言源碼的測試代碼(這是理所應(yīng)當(dāng)?shù)氖虑椋?/p>
然后編寫動態(tài)語言源碼 :)
在XML配置文件中使用相應(yīng)的<lang:language/>
元素定義dynamic-language-backed bean。當(dāng)然你也可以使用Spring API,以編程的方式來定義---本章并不會涉及到這種高級的配置方式,你可以直接閱讀源碼來獲得相應(yīng)的指示)。注意這是一個迭代的步驟。每一個動態(tài)語言的源文件至少對應(yīng)一個bean定義(同一個動態(tài)語言的源文件當(dāng)然可以在多個bean定義中引用)。
前面兩步(測試并編寫動態(tài)語言源文件)超出了本章的范疇。請參考你所選動態(tài)語言相關(guān)的語言規(guī)范或者參考手冊,并繼續(xù)開發(fā)你的動態(tài)語言的源文件。不過你應(yīng)該首先閱讀本章的剩下部分,因為Spring(動態(tài)語言支持)對動態(tài)語言源文件的內(nèi)容有一些(小小的)要求。
最后一步包括如何定義dynamic-language-backed bean定義,每一個要配置的bean對應(yīng)一個定義(這和普通的Javabean配置沒有什么區(qū)別)。但是,對于容器中每一個需要實例化和配置的類,普通的Javabean配置需要指定全限定名,對于dynamic language-backed bean則使用<lang:language/>
元素取而代之。
每一種支持的語言都有對應(yīng)的<lang:language/>
元素
<lang:jruby/>
(JRuby)
<lang:groovy/>
(Groovy)
<lang:bsh/>
(BeanShell)
對于配置中可用的確切的屬性和子元素取決于具體定義bean的語言(后面和特定語言有關(guān)的章節(jié)會揭示全部內(nèi)幕)。
Spring對動態(tài)語言支持中最引人注目的價值在于增加了對 'refreshable bean' 特征的支持。
refreshable bean是一種只有少量配置的dynamic-language-backed bean。dynamic-language-backed bean 可以監(jiān)控底層源文件的變化,一旦源文件發(fā)生改變就可以自動重新加載(例如開發(fā)者編輯文件并保存修改)。
這樣就允許開發(fā)者在應(yīng)用程序中部署任意數(shù)量的動態(tài)語言源文件,并通過配置Spring容器來創(chuàng)建動態(tài)語言源文件所支持的bean(使用本章所描述的機制)。以后如果需求發(fā)生改變,或者一些外部因素起了作用,這樣就可以簡單的編輯動態(tài)語言源文件,而這些文件中的變化會反射為bean的變化。而這些工作不需要關(guān)閉正在運行的應(yīng)用(或者重新部署web應(yīng)用)。dynamic-language-backed bean能夠自我修正,從已改變的動態(tài)語言源文件中提取新的狀態(tài)和邏輯。
注意,該特征默認值為off(關(guān)閉)。
下面讓我們看一個例子,體驗一下使用refreshable bean是多么容易的事情。首先要啟用refreshable bean特征,只需要在bean定義的 <lang:language/>
元素中指定一個附加屬性。假設(shè)我們繼續(xù)使用前文中的 例子,那么只需要在Spring的XML配置文件中進行如下修改以啟用refreshable bean:
<beans> <!-- this bean is now 'refreshable' due to the presence of the 'refresh-check-delay' attribute --> <lang:groovy id="messenger" refresh-check-delay="5000" <!-- switches refreshing on with 5 seconds between checks --> script-source="classpath:Messenger.groovy"> <lang:property name="message" value="I Can Do The Frug" /> </lang:groovy> <bean id="bookingService" class="x.y.DefaultBookingService"> <property name="messenger" ref="messenger" /> </bean> </beans>
這就是所有你需要做的事情。 'messenger'
bean定義中的'refresh-check-delay'
屬性指定了刷新bean的時間間隔,在這個時間段內(nèi)的底層動態(tài)語言源文件的任何變化都會刷新到對應(yīng)的bean上。通過給該屬性賦一個負值即可關(guān)閉該刷新行為。注意在默認情況下,該刷新行為是關(guān)閉的。如果你不需要該刷新行為,最簡單的辦法就是不要定義該屬性。
運行以下應(yīng)用程序可以體驗refreshable特征:請執(zhí)行接下來這段代碼中的'jumping-through-hoops-to-pause-the-execution'小把戲。System.in.read()
的作用是暫停程序的執(zhí)行,這個時候去修改底層的動態(tài)語言源文件,然后程序恢復(fù)執(zhí)行的時候觸發(fā)dynamic-language-backed bean的刷新。
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.scripting.Messenger; public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); Messenger messenger = (Messenger) ctx.getBean("messenger"); System.out.println(messenger.getMessage()); // pause execution while I go off and make changes to the source file... System.in.read(); System.out.println(messenger.getMessage()); } }
假設(shè)對于這個例子,所有調(diào)用Messenger
實現(xiàn)中getMessage()
方法的地方都被修改:比如將message用引號括起來。下面是我在程序執(zhí)行暫停的時候?qū)?code class="filename">Messenger.groovy源文件所做的修改:
package org.springframework.scripting class GroovyMessenger implements Messenger { private String message = "Bingo" public String getMessage() { // change the implementation to surround the message in quotes return "'" + this.message + "'" } public void setMessage(String message) { this.message = message } }
在這段程序執(zhí)行的時候,在輸入暫停之前的輸出是I Can Do The Frug
。在修改并保存了源文件之后,程序恢復(fù)執(zhí)行,再次調(diào)用dynamic-language-backed Messenger
的getMessage()
方法的結(jié)果為'I Can Do The Frug'
(注意新增的引號)。
有一點很重要,如果上述對腳本的修改發(fā)生在'refresh-check-delay'
值的時間范圍內(nèi)并不會觸發(fā)刷新動作。同樣重要的是,修改腳本并不會馬上起作用,而是要到該動態(tài)語言實現(xiàn)的bean的相應(yīng)的方法被調(diào)用時才有效。只有動態(tài)語言實現(xiàn)的bean的方法被調(diào)用的時候才會檢查底層源文件是否修改了。刷新腳本產(chǎn)生的任何異常(例如發(fā)生編譯錯誤,或者腳本文件被刪除了)都會在調(diào)用的代碼中拋出一個致命異常。
前面描述的refreshable bean的行為并不會作用于使用<lang:inline-script/>
元素定義的動態(tài)語言源文件(請參考第?24.3.1.3?節(jié) “內(nèi)置動態(tài)語言源文件”這一節(jié))。而且它只作用于那些可以檢測到底層源文件發(fā)生改變的bean。例如,檢查文件系統(tǒng)中的動態(tài)語言源文件的最后修改日期。
Spring動態(tài)語言支持還提供了直接在bean定義中直接嵌入動態(tài)語言源碼的功能。通過<lang:inline-script/>
元素,可以在Spring的配置文件中直接定義動態(tài)語言源文件。下面的例子或許可以將嵌入腳本特征表達的更清楚:
<lang:groovy id="messenger"> <lang:inline-script> package org.springframework.scripting.groovy; import org.springframework.scripting.Messenger class GroovyMessenger implements Messenger { String message } </lang:inline-script> <lang:property name="message" value="I Can Do The Frug" /> </lang:groovy>
直接在Spring的配置文件中定義動態(tài)語言源碼的是否是最佳實踐這個問題先放在一邊,<lang:inline-script/>
元素在某些場景下還是相當(dāng)有用的。例如,給Spring MVC的Controller
快速添加一個Spring Validator
實現(xiàn)。如果采用內(nèi)置源碼的方式只需要片刻時間就可以搞定(請參見第?24.4.2?節(jié) “Validator的腳本化”這一節(jié)的示例)。
下面這個例子是一個基于JRuby的bean,這個例子直接在Spring的XML配置文件中定義了源碼,并使用了inline:
符號。(注意其中使用 <符號來表示'<'
字符。這個例子中如果在內(nèi)置源碼周圍定義一個<![CDATA[]]>
就更好了。)
<lang:jruby id="messenger" script-interfaces="org.springframework.scripting.Messenger"> <lang:inline-script> require 'java' include_class 'org.springframework.scripting.Messenger' class RubyMessenger < Messenger def setMessage(message) @@message = message end def getMessage @@message end end </lang:inline-script> <lang:property name="message" value="Hello World!" /> </lang:jruby>
關(guān)于Spring動態(tài)語言支持有一個要點必須引起注意:目前對dynamic-language-backed bean還不可能提供構(gòu)造器參數(shù)的支持(也就是說對于dynamic-language-backed bean的構(gòu)造器注入無效)。 只是為了將構(gòu)造器和屬性的特殊處理100%說清楚,下面混合了代碼和配置的例子是無法運作的。
// from the file 'Messenger.groovy' package org.springframework.scripting.groovy; import org.springframework.scripting.Messenger class GroovyMessenger implements Messenger { GroovyMessenger() {} // this constructor is not available for Constructor Injection GroovyMessenger(String message) { this.message = message; } String message String anotherMessage }
<lang:groovy id="badMessenger"
script-source="classpath:Messenger.groovy">
<!-- this next constructor argument will *not* be injected into the GroovyMessenger
-->
<!-- in fact, this isn't even allowed according to the schema -->
<constructor-arg value="This will *not* work" />
<!-- only property values are injected into the dynamic-language-backed object -->
<lang:property name="anotherMessage" value="Passed straight through to the dynamic-language-backed object" />
</lang>
實際上這種局限性并沒有表現(xiàn)的那么明顯,因為setter注入的方式是開發(fā)人員更青睞的方式(至于哪種注入方式更好,這個話題我們還是留到以后再討論吧)。
來自JRuby官方網(wǎng)頁...
“ JRuby是Ruby語言的純Java實現(xiàn)。 ”Spring一直以來的崇尚的哲學(xué)是提供選擇性,因此Spring動態(tài)語言支持特征也支持使用JRuby語言定義的bean。JRuby語言當(dāng)然基于Ruby語言,支持內(nèi)置正則表達式,塊(閉包),以及其他很多特征,這些特征對于某些域問題提供了解決方案,可以讓開發(fā)變的更容易。
Spring對JRuby動態(tài)語言支持的有趣的地方在于:對于<lang:ruby>
元素'script-interfaces'
屬性指定的接口,Spring為它們創(chuàng)建了JDK動態(tài)代理實現(xiàn)(這也是你使用JRuby實現(xiàn)的bean,必須為該屬性指定至少一個接口并編程實現(xiàn)的原因)。
首先我們看一個使用基于JRuby的bean的可工作的完整示例。下面是使用JRuby實現(xiàn)的Messenger
接口(本章之前定義過了,為了方便你閱讀,下面重復(fù)定義該接口)。
package org.springframework.scripting; public interface Messenger { String getMessage(); }
require 'java' class RubyMessenger include org.springframework.scripting.Messenger def setMessage(message) @@message = message end def getMessage @@message end end # this last line is not essential (but see below) RubyMessenger.new
下面是Spring的XML配置,其內(nèi)容定義了RubyMessenger
(JRuby bean)的實例。
<lang:jruby id="messageService" script-interfaces="org.springframework.scripting.Messenger" script-source="classpath:RubyMessenger.rb"> <lang:property name="message" value="Hello World!" /> </lang:jruby>
注意JRuby源碼的最后一行('RubyMessenger.new'
)。在Spring動態(tài)語言支持的上下文之下使用JRuby的時候,我們鼓勵你實例化并返回一個JRuby類的實例。如果你打算將其作為你的JRuby源碼的執(zhí)行結(jié)果,并將其作為dynamic-language-backed bean,只需要簡單的實例化你的JRuby類就可以達到這樣的效果,如下面源文件的最后一行:
require 'java' include_class 'org.springframework.scripting.Messenger' # class definition same as above... # instantiate and return a new instance of the RubyMessenger class RubyMessenger.new
如果你忘記了這點,并不代表以前所有的努力白費了,不過Spring會以反射的方式掃描你的JRuby的類型表示,并找出一個類,然后進行實例化。這個過程的速度是相當(dāng)快的,可能你永遠都不會感覺到延遲,但是只需要象前面的例子那樣在你的JRuby的腳本最后添加一行就可以避免這樣的事情,何樂而不為呢?如果不提供這一行,或者如果Spring在你的JRuby腳本中無法找到可以實例化的類,JRuby的解釋器執(zhí)行源碼結(jié)束后會立刻拋出ScriptCompilationException
異常。下面的代碼中可以立刻發(fā)現(xiàn)一些關(guān)鍵的文本信息,這些文本信息標(biāo)識了導(dǎo)致異常的根本原因(如果Spring容器在創(chuàng)建的 dynamic-language-backed bean的時候拋出以下異常, 在相應(yīng)的異常堆棧中會包括以下文本信息,希望這些信息能夠幫助你更容易定位并矯正問題):
org.springframework.scripting.ScriptCompilationException: Compilation of JRuby script returned ''
為了避免這種錯誤,將你打算用作JRuby-dynamic-language-backed bean(如前文所示)的類進行實例化,并將其返回。請注意在JRuby腳本中實際上可以定義任意數(shù)目的類和對象,重要的是整個源文件應(yīng)該返回一個對象(用于Spring的配置)。
第?24.4?節(jié) “場景” 這一節(jié)提供了一些場景,在這些場景下也許你會打算采用基于JRuby的bean.
來自Groovy官方網(wǎng)頁...
“ Groovy是一門針對Java 2平臺的敏捷的動態(tài)語言,Python、Ruby、Smalltalk這類語言中不少受人喜愛的特征都被囊括其中,并以Java風(fēng)格的語法展現(xiàn)給Java開發(fā)者。 ”如果你是以從上到下的方式一直讀到這一章,你應(yīng)該已經(jīng)看到了一個Groovy-dynamic-language-backed bean的示例。接下來我們來看另外一個例子(還是選自Spring的測試套件)。
Groovy需要1.4以上的JDK。
package org.springframework.scripting; public interface Calculator { int add(int x, int y); }
下面是使用Groovy實現(xiàn)的Calculator
接口。
// from the file 'calculator.groovy'
package org.springframework.scripting.groovy
class GroovyCalculator implements Calculator {
int add(int x, int y) {
x + y
}
}
<-- from the file 'beans.xml' -->
<beans>
<lang:groovy id="calculator" script-source="classpath:calculator.groovy"/>
</beans>
最后是一個小應(yīng)用程序,用于測試上面的配置。
package org.springframework.scripting; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Main { public static void Main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); Calculator calc = (Calculator) ctx.getBean("calculator"); System.out.println(calc.add(2, 8)); } }
運行上面的程序最終輸出結(jié)果(毫無疑問)為10
。(令人激動的例子,是吧?記住我們的目的是為了闡述概念。更復(fù)雜的例子請參考動態(tài)語言的示例項目,或者參考本章最后列出的第?24.4?節(jié) “場景”)。
有一點很重要,那就是你不要 在一個Groovy源文件中定義兩個以上的class。雖然Groovy允許這樣做,但是是一個很不好的實踐,為了保持一致性,你應(yīng)該尊重標(biāo)準(zhǔn)的Java規(guī)范(至少作者是這樣認為的):一個源文件只定義一個(public)類。
GroovyObjectCustomizer
接口實際上是一個回調(diào),
它允許你將附屬的創(chuàng)建邏輯添加到創(chuàng)建Groovy bean的過程中。
例如,該接口的實現(xiàn)能夠調(diào)用任何需要的初始化方法,
或者設(shè)置一些缺省的屬性值,或者指定自定義的MetaClass
。
public interface GroovyObjectCustomizer { void customize(GroovyObject goo); }
Spring框架將會初始化Groovy bean示例,然后將已經(jīng)創(chuàng)建的GroovyObject
對象傳到到指定的GroovyObjectCustomizer
接口。
通過GroovyObject
你可以做任意想做的事情。大部分
人也許都想通過該回調(diào)傳入一個定制的MetaClass
,下面你將看到一個這樣的例子。
public final class SimpleMethodTracingCustomizer implements GroovyObjectCustomizer { public void customize(GroovyObject goo) { DelegatingMetaClass metaClass = new DelegatingMetaClass(goo.getMetaClass()) { public Object invokeMethod(Object object, String methodName, Object[] arguments) { System.out.println("Invoking '" + methodName + "'."); return super.invokeMethod(object, methodName, arguments); } }; metaClass.initialize(); goo.setMetaClass(metaClass); } }
完整討論Groovy的元編程已經(jīng)超出了本參考手冊的范疇。建議直接查閱Groovy參考手冊的有關(guān)章節(jié),
或者通過在線查詢:目前有大量的關(guān)于這方面的文章。
如果你使用Spring 2.0的命名空間支持功能,使用GroovyObjectCustomizer
將會變得非常簡單。
<!-- define theGroovyObjectCustomizer
just like any other bean --> <bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer" /> <!-- ... and plug it into the desired Groovy bean via the 'customizer-ref
' attribute --> <lang:groovy id="calculator" script-source="classpath:org/springframework/scripting/groovy/Calculator.groovy" customizer-ref="tracingCustomizer" />
如果你沒有使用Spring 2.0的命名空間支持,你仍然可以使用GroovyObjectCustomizer
的功能。
<bean id="calculator" class="org.springframework.scripting.groovy.GroovyScriptFactory">
<constructor-arg value="classpath:org/springframework/scripting/groovy/Calculator.groovy"/>
<!-- define the GroovyObjectCustomizer
(as an inner bean) -->
<constructor-arg>
<bean id="tracingCustomizer" class="example.SimpleMethodTracingCustomizer" />
</constructor-arg>
</bean>
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
來自BeanShell官方網(wǎng)頁...
“ BeanShell是一個用Java實現(xiàn)的小型免費嵌入式Java源碼解釋器,支持動態(tài)語言特征。BeanShell動態(tài)執(zhí)行標(biāo)準(zhǔn)的Java語法,并進行了擴展,帶來一些常見的腳本的便利,如在Perl和JavaScript中的寬松類型、命令、方法閉包等等。 ”
和Groovy相比,基于BeanShell的bean定義需要的配置要多一些。Spring對BeanShell動態(tài)語言支持的有趣的地方在于:對于<lang:bsh>
元素的'script-interfaces'
屬性指定的接口,Spring為它們創(chuàng)建了JDK動態(tài)代理實現(xiàn)(這也是你使用BeanShell實現(xiàn)的bean,必須為該屬性指定至少一個接口并編程實現(xiàn)的原因)。這意味著所有調(diào)用 BeanShell-backed對象的方法,都要通過JDK動態(tài)代理調(diào)用機制。
首先我們看一個使用基于BeanShell的bean的可工作的完整示例。它實現(xiàn)了本章之前定義的Messenger
接口(為了方便閱讀,下面重復(fù)定義該接口)。
package org.springframework.scripting; public interface Messenger { String getMessage(); }
Here is the BeanShell 'implementation' (the term is used loosely here) of the
Messenger
interface.
下面是BeanShell的實現(xiàn)的Messenger
接口。
String message; String getMessage() { return message; } void setMessage(String aMessage) { message = aMessage; }
下面的Spring XML定義了上述“類”的一個“實例”(這里對術(shù)語的使用非常的隨意)。
<lang:bsh id="messageService" script-source="classpath:BshMessenger.bsh" script-interfaces="org.springframework.scripting.Messenger"> <lang:property name="message" value="Hello World!" /> </lang:bsh>
第?24.4?節(jié) “場景”一節(jié)中提供了一些場景,在這樣的場景下你也許打算采用基于BeanShell的bean。