取得領先
VMware 提供培訓和認證,以加速您的進度。
了解更多自 Spring 2.0 引入 Spring 動態語言支援以來,它一直是 Groovy 的一個有吸引力的整合點,而 Groovy 提供了一個豐富的環境來定義領域特定語言 (DSL)。但是 Spring 參考手冊中的 Groovy 整合範例在範圍上受到限制,並且沒有展示 Spring 中針對 DSL 整合的功能。在本文中,我將展示如何使用這些功能,並以 Grails 發行版中的 Groovy DSL 將 bean 定義添加到現有的 ApplicationContext 作為一個範例。
Spring 動態語言整合的基本功能在 XML 中的 "lang" 命名空間中公開。您可以做的最直接的事情是將 Spring 組件定義為 Groovy bean,在一個單獨的文件中或在 XML 中內聯。Spring 參考指南中涵蓋了此功能 (http://static.springframework.org/spring/docs/2.5.x/reference/index.html),因此我們不需要過多地詳細介紹,但為了完整起見,我們不妨看一個快速範例。
假設我們有一個 Java 介面
public interface Messenger {
String getMessage();
}
這是 Groovy 中實現該介面的內聯 bean 定義
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<lang:groovy id="messenger">
<![CDATA[
class GroovyMessenger implements spring.Messenger {
def String message;
}
]]>
</lang:groovy>
</beans>
請注意,由於 Groovy 為所有屬性定義了公共 getter 和 setter,因此我們實際上不需要顯式地編寫 getMessage() 方法。另請記住,Spring 動態語言支援的一個特性是,內聯 Groovy 程式碼也可以提取到一個單獨的原始碼檔案中(使用 lang:groovy 元素的 script-source 屬性)。
Spring 動態語言支援的另一個特性是,指令碼可以超越簡單地定義一個類別。您還可以編寫一個 Groovy 指令碼,該指令碼執行一些處理,並在最後傳回一個物件的實例。例如,如果我們已經有一個名為 JavaMessenger 的 Messenger 實作
<lang:groovy id="messenger">
<![CDATA[
def messenger = new JavaMessenger("Hello World!")
messenger
]]>
</lang:groovy>
這具有公開具有特定訊息的 JavaMessenger 實例的效果 - 一個微不足道的範例,但可以很好地了解公開的功能。使用這種技術,我們可以超越 Spring 中正常的 bean 建立模式,並在傳回物件之前在指令碼中進行盡可能多的處理。
在幕後,Spring 正在建立 groovy.util.Script 的實例,其 run() 方法傳回指令碼末尾的物件。當我們開始考慮如何整合 DSL 時,這將變得重要。
我們需要查看的下一個功能是能夠在 Groovy 物件公開為 Spring 組件之前對其進行自訂。我相信,此功能是在 Rod Johnson 和 Guillaume Laforge 在 Spring 2.0 發佈開始時的某次會議上添加的(不在 2.0 中)。Guillaume 對領域特定語言的興趣使他觀察到 Spring 處於一個良好的位置,能夠在任何人有機會使用 Groovy 物件(或其類別)之前,對其進行操作並新增行為,並且由於 Groovy 是一種動態語言,因此這是一種非常強大的慣用語法。
他們提出的機制是 GroovyObjectCustomizer 介面,可以在 Groovy 物件實例化之後以及(如果是 Script)在執行之前將其應用於 Groovy 物件。該介面如下所示
public interface GroovyObjectCustomizer {
void customize(GroovyObject goo);
}
它允許我們在物件發佈之前對物件的方法和屬性進行操作。
要應用自訂程式,我們只需要在 Groovy bean 定義中添加對它的引用
<lang:groovy id="messenger" script-source="classpath:..." customizer-ref="customizer"/>
<bean id="customizer" class="..."/>
Grails 有一個不錯的 Spring 組件 DSL,稱為 BeanBuilder(請參閱此處以了解更多詳細資訊)。它允許我們以一種非常自然和簡潔的方式在 Groovy 中構建 Spring ApplicationContext。根據 Graeme Rocher 的說法,在最新版本的 Grails 中,BeanBuilder 也可以在不依賴任何 Web 框架的情況下工作 - 您只需要 Grails Core 和 Groovy 在您的類別路徑中。因此,現在是看看我們是否可以將 BeanBuilder 與 Spring 整合的好時機(正如 Spring 論壇 此處 也指出的那樣)。(實際上,如果沒有 servlet API 和 Spring webflow jar,我無法使用 Grails 1.0-rc1 使範例工作,但可能它會在 rc2 或 1.0 final 中工作。)
Groovy 中領域特定語言中的表達式通常採用閉包的形式,因此使用 Spring 整合中的 Script 模式來定義閉包是很自然的。在 BeanBuilder 的情況下,它看起來像這樣
<lang:groovy id="beans">
<![CDATA[
beans = {
messenger(JavaMessenger) {
message = "Hello World!"
}
// ... more bean definitions here ...
}
]]>
</lang:groovy>
這會產生一個 Script 物件,該物件本身會傳回一個包含 bean 定義的閉包(稱為 "beans")。其中一個 bean 定義是我們的朋友 messenger。我們理想情況下希望能夠採用這些 bean 定義並將它們與當前的 ApplicationContext 合併。為此,我們需要使用 GroovyObjectCustomizer。
public class BeanBuilderClosureCustomizer implements GroovyObjectCustomizer {
public void customize(GroovyObject goo) {
createApplicationContext(goo.run())
}
private ApplicationContext createApplicationContext(Closure value) {
BeanBuilder builder = new BeanBuilder()
builder.beans(value)
builder.createApplicationContext()
}
}
它尚未對其建立的應用程式上下文執行任何操作 - 只是建立它並使其蒸發。它也沒有執行任何錯誤檢查,但是我們可以稍後添加它。自訂程式是用 Groovy 編寫的,因此我們可以只呼叫 goo.run() 而無需轉換為 Script。
public class BeanBuilderClosureCustomizer implements GroovyObjectCustomizer {
public void customize(GroovyObject goo) {
addbeanDefinitions(createApplicationContext(goo.run()))
}
private void addBeanDefinitions(ApplicationContext context) {
DefaultListableBeanFactory scriptBeanFactory = context.autowireCapableBeanFactory
for (name in scriptBeanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = scriptBeanFactory.getBeanDefinition(name)
applicationContext.autowireCapableBeanFactory.registerBeanDefinition(name, definition)
}
}
// createAppicationContext defined here....
}
還有比這更簡單的嗎?
到目前為止,將所有內容放在一起,我們可以載入此 Spring 配置
<beans>
<lang:groovy id="beans" customizer-ref="customizer">
<![CDATA[
beans = {
messenger(JavaMessenger) {
message = "Hello World!"
}
// ... more bean definitions here ...
}
]]>
</lang:groovy>
<bean id="customizer" class="BeanBuilderClosureCustomizer"/>
</beans>
然後取出 messenger 並使用它。在範例中(請參閱附件),我們讓 Spring 2.5 TestContextFramework 負責建立 ApplicationContext 並將相依性注入到測試案例中(因此無需任何相依性查找)。
作為最終調整,為了使我們的 BeanBuilderClosureCustomizer 更有用,我們將修改它以使用封閉的 ApplicationContext 作為 BeanBuilder 中 bean 定義的父項。為此,我們只需要在我們的自訂程式中引用父項,因此我們需要實作 ApplicationContextAware 並使用該引用來構建 BeanBuilder
public class BeanBuilderClosureCustomizer implements GroovyObjectCustomizer,
ApplicationContextAware {
def ApplicationContext applicationContext;
public void customize(GroovyObject goo) {
addbeanDefinitions(createApplicationContext(goo.run()))
}
private ApplicationContext createApplicationContext(Closure value) {
BeanBuilder builder = new BeanBuilder(applicationContext)
builder.beans(value)
builder.createApplicationContext()
}
// addBeanDefinitions defined here....
}
由於 BeanBuilderClosureCustomizer 是用 Groovy 編寫的,因此我們不需要為 applicationContext 屬性定義顯式的 getter 和 setter - 它們由 Groovy 自動產生。
BeanBuilderClosureCustomizer 現在可以使用了(可能需要進行一些額外的錯誤檢查)。而 Groovy 真正了不起的地方在於,它可以作為 JVM 位元組碼編譯並發佈到 jar 檔案中。因此,我需要做的就是確保在封裝專案時發佈產生的類別檔案。範例只是通過將 Groovy bean 編譯到與 Java 編譯器使用的目標目錄相同的目錄中來實現這一點。
在我們的 Groovy DSL 中引用父上下文中的 bean 也會非常棒。Grails 允許我們使用 BeanBuilder DSL 中的 "ref" 關鍵字來執行此操作,例如
<lang:groovy id="beans" customizer-ref="customizer">
<![CDATA[
beans = {
messenger(JavaMessenger) {
message = ref("helloMessage")
}
// ... more bean definitions here ...
}
</lang:groovy>
在這裡,我們從父上下文中的 bean 定義中載入了訊息。
要執行範例,只需解壓縮zip 檔案,或使用 Eclipse 將其導入到現有的工作區中(檔案->導入...->現有的專案...)。如果您有適用於 Eclipse 的 m2 外掛程式,它應該可以立即使用。如果沒有,您可以使用 m2 Eclipse 外掛程式來產生 Eclipse 元資料 ("mvn eclipse:eclipse")。如果您未使用 Maven 或 Eclipse,則您需要自己解決,但您可以在 pom.xml 中找到最上層的專案相依性。
由於專案在單元測試中使用 JSR-250 註釋進行相依性注入,因此您需要提供該 API。最簡單的方法是使用 Java 6 來執行和編譯。例如,在 *NIX 命令列上
$ JAVA_HOME=<path-to-JDK-1.6> mvn clean test
附註:實際上,當我上面說我可以載入包含內聯指令碼的配置時,我說謊了 - 它在 Spring 2.5 中不起作用,因為在 2.5.1 中修復了一個錯誤(請參閱JIRA)。解決方法(如範例中所示)是使用外部檔案來儲存指令碼。