# 實戰 Groovy: 用 Groovy 生成器作標記
_拋開標記語言的細節,聚焦應用程序的內容_
Groovy 生成器讓您能夠利用諸如 Swing 這樣的框架來模擬標記語言(如 XML、HTML、Ant) 任務以及 GUI。它們對于快速原型化非常有 用,并且正像 Andrew Glover 這個月在“_實戰 Groovy_”專欄中向您展示的那樣,當您馬上需要可消費的標記時,它們是數據綁定框架的一種便利的替代方案。
幾個月前,當我最初撰寫有關 [實戰 Groovy: 用 Groovy 進行 Ant 腳本編程](/developerworks/cn/java/j-pg12144.html)的文章時,我提及了 Groovy 中的 _生成器_概念。在那篇文章里,我向您展示了,使用一個叫做 `AntBuilder`的 Groovy 類,構建富有表現力的 Ant 構建文件是多么容易。本文中,我將深入 Groovy 生成器的世界,向您展示您還能用這些強大的類做些什么。
## 用生成器進行構建
Groovy 生成器讓您能夠利用諸如 Swing 這樣的框架來模擬標記語言(如 XML、HTML、Ant) 任務以及 GUI。使用生成器,您可以迅速地創 建復雜的標記(如 XML),而無須理會 XML 本身。
生成器的范例非常簡單。生成器的實例的方法表示該標記(如 HTML 中的 `<body>`標 簽)的元素。方法的創建于閉包中的對象表示子節點(例如,`<body>`標簽中所包含的 `<p>`標簽)。
為了便于您查看這一過程,我將創建一個簡單的生成器,以程序方式來表示一個具有清單 1 所示結構的 XML 文檔。
## 關于本系列文章
在開發實踐中采用任何工具的關鍵是,了解何時使用這些工具,何時將其棄而不用。腳本語言可能是對工具包的一個功能強大的擴充,但是只有 在正確應用于適當的環境時才是如此。總之,[_實戰 Groovy_](/developerworks/cn/views/java/articles.jsp?view_by=search&search_by=%E5%AE%9E%E6%88%98+Groovy%3A)系列文章旨在展示 Groovy 的實際使用,以及何時和如何成功應用它。
##### 清單 1\. 簡單 XML 結構
```
<person>
<name first="Megan" last="Smith">
<age>32</age>
<gender>female</gender>
</name>
<friends>
<friend>Julie</friend>
<friend>Joe</friend>
<friend>Hannah</friend>
</friends>
</person>
```
要表示這個結構非常簡單。首先將 `person`方法連接到生成器實例,現在它表示 XML 的根節點,即 `<person>`。要創建子節點,我創建一個閉包并聲明一個名叫 `name`的新對象(它接收 `map`形式的參數。順便說一下,這些參數是元素的屬性的基礎。
接下來,在 `name`對象中,將兩個附加對象連接到閉包,一個對象是 `age`,另 一個是 `gender`,它們對應于 `<name>`的類似子元素。您明白其中的訣竅了么?確實很簡單。
`<friends>`元素是 `<person>`的兄弟元素,于是我跳出這個閉包,聲明了一個 `friends`對象,當然,還附加了一個集合了多個 `friend`元素的閉包,如清單 2 所示。
##### 清單 2\. 生成器是如此的簡單
```
import groovy.xml.*
import java.io.*
class XMLBuilder{
static void main(args) {
writer = new StringWriter()
builder = new MarkupBuilder(writer)
friendnames = [ "Julie", "Joey", "Hannah"]
builder.person() {
name(first:"Megan", last:"Smith") {
age("33")
gender("female")
}
friends() {
for (e in friendnames) { friend(e) }
}
}
println writer.toString()
}
}
```
如您所見,這里的 Groovy 表示非常優雅,且易于映射到相應的標記表示。在底層,Groovy 顯然在處理煩人的標記元素(如 < and >),使我們可以將更多精力放在內容上,而不必過分在意結構的細節。
* * *
## 顯示 HTML
生成器也可以有助于構建 HTML,這在開發 Groovlet 時可以派上用場。如同小菜一碟,假設我要創建一個如清單 3 所示的 HTML 頁面。
##### 清單 3\. HTML 101
```
<html>
<head>
<title>Groov'n with Builders</title>
</head>
<body>
<p>Welcome to Builders 101\. As you can see this Groovlet is fairly simple.</p>
</body>
</html>
```
我可以輕易地將它編碼在 Groovy 中,清單 4 所示。
##### 清單 4\. HTML in Groovy 101
```
import groovy.xml.*
import java.io.*
class HTMLBuilderExample{
static void main(args) {
writer = new StringWriter()
builder = new MarkupBuilder(writer)
builder.html(){
head(){
title("Groov'n with Builders"){}
}
body(){
p("Welcome to Builders 101\. As you can see " +
"this Groovlet is fairly simple.")
}
}
println writer.toString()
}
```
來點有意思的,讓我們再看看用生成器建立一個成熟的 GUI 有多么容易。前面我曾提到過,Groovy 的 `SwingBuilder`使它能夠以一種極為簡單的方式構造 GUI。您可以查閱清單 5 中 `SwingBuilder`是如何工作的。
##### 清單 5\. Groovy 中的 GUI 生成器真的很“GROOVY”(很“棒”)
```
import java.awt.FlowLayout
import javax.swing.*
import groovy.swing.SwingBuilder
class SwingExample{
static void main(args) {
swinger = new SwingBuilder()
langs = ["Groovy", "Ruby", "Python", "Pnuts"]
gui = swinger.frame(title:'Swinging with Groovy!', size:[290,100]) {
panel(layout:new FlowLayout()) {
panel(layout:new FlowLayout()) {
for (lang in langs) {
checkBox(text:lang)
}
}
button(text:'Groovy Button', actionPerformed:{
swinger.optionPane(message:'Indubitably Groovy!')
.createDialog(null, 'Zen Message').show()
})
button(text:'Groovy Quit', actionPerformed:{ System.exit(0)})
}
}
gui.show()
}
}
```
圖 1 顯示了上面的結果,還不錯吧?
##### 圖 1\. Groovy 中神奇的 GUI 編程

可以想像,對于原型化,`SwingBuilder`是一個多么強大的工具,不是么?
* * *
## 一些事實
這些例子雖然瑣碎,卻也有趣。我希望我能讓您明白,Groovy 的生成器可以讓您避免特定語言(如 XML)中的底層標記。顯然,有時避免 XML 或 HTML 會更好,并且,那些標記協助器(facilitator)對 Java 平臺來說并不陌生。例如,我最喜歡的 XML 協助框架是 JiBX。
使用 JiBX,您可以輕易地將 XML 結構映射到對象模型,_反之亦然_。綁定是個強大的范例,有不計其數的類似工具擁有此功能,如 JAXB、 Castor 和 Zeus 等。
綁定框架的惟一缺點是,它們 _恐怕_要耗費不少時間。幸運的是,您可以使用 Groovy 的生成器作為一個 _較簡單的_解決方案,這在某些情況下是有效的。
* * *
## 用生成器進行偽綁定
假設有一個英文詞典的簡單數據庫。有一個表用于 `word`,另一個表用于 `definition` ,最后還有一個表用于 `synonym`。圖 2 是這個數據庫的簡單表示。
##### 圖 2\. 詞典數據庫

如您所見,這個數據庫非常直觀:`word`與 `definition`和 `synonym`具有一對多的關系。
詞典數據庫擁有一個消費者,他在尋求一種表示數據庫內容的關鍵方面的 XML 結構。所尋求的 XML 結構如清單 6 所示。
##### 清單 6\. 可采用的詞典 XML
```
<words>
<word spelling="glib" partofspeech="adjective">
<defintions>
<defintion>Performed with a natural, offhand ease.</defintion>
<defintion>Marked by ease and fluency of speech or writing that often suggests
or stems from insincerity, superficiality, or deceitfulness</defintion>
</defintions>
<synonyms>
<synonym spelling="artful"/>
<synonym spelling="urbane"/>
</synonyms>
</word>
</words>
```
如果選擇使用 JiBX 這樣的綁定框架來解決這個問題,則很可能需要創建一些中間對象模型,以從關系模型到達最終的 XML 模型。然后必須將數據庫內容讀取到對象模型中,并請求底層框架將其內部的結構編組為 XML 格式。
這一過程內含了將對象結構映射到 XML 格式的步驟(使用所需的框架過程)。某些框架,如 JAXB,實際上是從 XML 和其他框架(如 JiBX )生成 Java 對象,允許您自定義自己的 Java 對象到 XML 格式的映射。總之,這都需要大量的工作。
并且,這是一項宏偉的計劃。我并不提倡避免使用綁定框架。這里,我要聲明:我已經預先警告過您。我計劃向您展示的是一個生成 XML 的便捷方式。
### 可消費的 XML 很簡單
使用 Groovy 的 `MarkupBuilder`,結合新的數據庫訪問框架 GroovySql,您可以輕易地生成可消費的 XML。您 所要做的只是計算出所需的查詢,并將結果映射到生成器實例 —— 然后,您馬上就可以得到表示詞典數據庫內容的 XML 文檔。
讓我們逐步來了解這一過程。首先,創建一個生成器實例,在本例中是 `MarkupBuilder`,因為您想要生成 XML。最外面的 XML 元 素(也就是“根”)是 `words`,這樣就創建了一個 `words`方法。在閉包里, 調用第一個查詢,并在迭代中將查詢結果映射到 `word`子節點。
接著,通過兩個新的查詢,創建 `word`的兩個子節點。創建一個 `definitions`對象,并在迭代中映射它,接著用同樣的方法處理 `synonyms`。
##### 清單 7\. 用生成器集合所有元素
```
import groovy.sql.Sql
import groovy.xml.MarkupBuilder
import java.io.File
import java.io.StringWriter
class WordsDbReader{
static void main(args) {
sql = Sql.newInstance("jdbc:mysql://localhost/words",
"words", "words", "org.gjt.mm.mysql.Driver")
writer = new StringWriter()
builder = new MarkupBuilder(writer)
builder.words() {
sql.eachRow("select word_id, spelling, part_of_speech from word"){ row |
builder.word(spelling:row.spelling, partofspeech:row.part_of_speech){
builder.definitions(){
sql.eachRow("select definition from definition where word_id =
${row.word_id}"){ defrow |
builder.definition(defrow.definition)
}
}
builder.synonyms(){
sql.eachRow("select spelling from synonym where word_id = ${row.word_id}"){
synrow |
builder.synonym(synrow.spelling)
}
}
}
}
}
new File("dbouuput.xml").withPrintWriter{ pwriter |
pwriter.println writer.toString()
}
}
}
```
### 結束語
這里,我向您展示的綁定解決方案似乎簡單得讓人難以置信,特別是以 Java 純化論者的觀點看來更是如此。盡管該解決方案不比使用綁定框架(如 JABX 和 JiBX) _更好_,但它確實更快一些 —— 而且,我主張使用這樣較簡單的方法。是不是我在 _簡單的_Java 代碼中做一些類似的事情?是的,但我敢肯定,某些時候我也不得不處理 XML。
用 Groovy 生成器進行開發的速度和簡易性,在調用標記的時候可大顯神威。例如,就像在第二個例子里展示的那樣,我可以馬上加快數據庫的 XML 表示。對于原型化,或者當需要以最少的開發時間和精力來產生可工作的解決方案時,生成器也是一個不錯的選擇。
在下個月的 _實戰 Groovy_中我會講些什么呢?哦,當然是在 Java 語言中使用 Groovy !
- 實戰 Groovy
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 2 部分
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 1 部分
- 實戰 Groovy: @Delegate 注釋
- 實戰 Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程
- 實戰 Groovy: 構建和解析 XML
- 實戰 Groovy: for each 剖析
- 實戰 Groovy: Groovy:Java 程序員的 DSL
- 實戰 Groovy: 關于 MOP 和迷你語言
- 實戰 Groovy: 用 curry 過的閉包進行函數式編程
- 實戰 Groovy: Groovy 的騰飛
- 實戰 Groovy: 在 Java 應用程序中加一些 Groovy 進來
- 實戰 Groovy: 用 Groovy 生成器作標記
- 實戰 Groovy: 用 Groovy 打造服務器端
- 實戰 Groovy: 使用 Groovy 模板進行 MVC 編程
- 實戰 Groovy: 用 Groovy 進行 JDBC 編程
- 實戰 Groovy: 用 Groovy 進行 Ant 腳本編程
- 實戰 Groovy: 用 Groovy 更迅速地對 Java 代碼進行單元測試
- alt.lang.jre: 感受 Groovy