# 實戰 Groovy: 使用 Groovy 模板進行 MVC 編程
_使用 Groovy 模板引擎框架簡化報表視圖_
視圖是 MVC 編程的一個重要部分,而 MVC 編程本身又是企業應用程序開發的一個重要組件。在這篇實戰 Groovy 的文章中,Andrew Glover 向您介紹了 Groovy 的模板引擎框架是如何用來簡化視圖編程的,并如何使您的代碼更加經久容易維護。
在最近的 _實戰 Groovy_系列中,我們已經介紹過 Groovy 是構建報表統計程序的一個非常好的工具。我們使用了一個校驗和報表統計應用程序的例子向您介紹了 [使用 Groovy 編寫 Ant 腳本](/developerworks/cn/java/j-pg12144.html)和一個數據庫報表統計來展示 [使用 GroovySql 進行 JDBC 編程](/developerworks/cn/java/j-pg01115.html)。這兩個示例統計報表都是在 Groovy 腳本中生成的,而且都具有一個相關的“視圖”。
在校驗和統計報表的例子中,視圖代碼有些混亂。更糟糕的是,它可能會對維護造成一些困難:如果我曾經希望修改視圖的某個特定方面,就必須修改腳本中的代碼。因此在本月的文章中,我將向您展示如何使用 Groovy 模板引擎和上一篇文章中的統計報表應用程序來對統計報表進行簡化。
模板引擎與 XSLT 很類似,可以產生模板定義的任何格式,包括 XML、HTML、SQL 和 Groovy 代碼。與 JSP 類似,模板引擎會簡化將視圖關注的內容分隔成單獨的實體,例如 JSP 或模板文件。例如,如果一個報表希望的輸出是 XML,那么您就可以創建一個 XML 模板,它可以包含一些占位符,在運行時替換為實際的值。模板引擎然后可以通過讀取該模板并在這些占位符和運行時的值之間建立映射來實現對模板的轉換。這個過程的輸出結果是一個 XML 格式的文檔。
## 關于本系列文章
在開發實踐中采用任何工具的關鍵是了解何時使用這些工具,何時將其棄而不用。腳本語言可能是對工具包的一個功能強大的擴充,但是只有在正確應用于適當的環境時才是如此。總之,_實戰 Groovy_系列文章是專門用來展示對 Groovy 的實際使用,以及何時和如何成功應用它。
在開始介紹 Groovy 模板之前,我想首先來回顧一下 Groovy 中 `String`類型的概念。所有的腳本語言都試圖使字符串的使用變得異常簡單。再次聲明,Groovy 并不是讓我們的技能逐步退化。在開始之前,請點擊本頁面頂部或底部的 **Code**圖標(或者參閱 [下載](#download)一節的內容),下載本文的源代碼。
## 不能獲得充足的 Strings
不幸的是,Java? 代碼中的 Strings 非常有限,不過 Java 5.0 應用程序承諾將有一些引人注目的新特性。現在,Groovy 解決了兩個最差的 Java `String`限制,簡化了編寫多行字符串和進行運行時替換的功能。有一些簡單的例子可以對 Groovy `String`類型進行加速,在本文中我們將使用這些例子。
如果您在普通的 Java 代碼中編寫一個多行的 `String`類型,就會最終要使用很多討厭的 `+`號,對嗎?但是在清單 1 中您可以看到,Groovy 不用再使用這些 `+`號了,這使您可以編寫更加清晰、簡單的代碼。
##### 清單 1\. Groovy 中的多行字符串
```
String example1 = "This is a multiline
string which is going to
cover a few lines then
end with a period."
```
Groovy 還支持 here-docs 的概念,如清單 2 所示。_here-doc_是創建格式化 `String`(例如 HTML 和 XML)的一種便利機制。注意 here-doc 語法與普通的 `String`聲明并沒有很大的不同,不過它需要類 Python 的三重引號。
##### 清單 2\. Groovy 中的 Here-docs
```
itext =
"""
This is another multiline String
that takes up a few lines. Doesn't
do anything different from the previous one.
"""
```
Groovy 使用 `GString`來簡化運行時替換。如果您不知道 GString 是什么,我可以確信您之前肯定見過它,而且還可能使用過它。簡單來說,`GString`允許您使用 _與 bash 類似的_`${}`語法進行替換。`GString`的優勢是您從來都不需要知道自己正在使用的是 `GString`類型;只需要在 Groovy 中簡單地編寫 `String`即可,就仿佛是在 Java 代碼中一樣。
## 關于模板引擎
模板引擎已經存在很長一段時間了,幾乎在每個現代語言中都可以找到。普通的 Java 語言具有 Velocity 和 FreeMarker;Python 有 Cheetah 和 Ruby ERB;Groovy 也有自己的引擎。要學習更多有關模板引擎的內容,請參閱 [參考資料](#resources)。
##### 清單 3\. Groovy 中的 GString
```
lang = "Groovy"
println "Uncle man, Uncle man, I dig ${lang}."
```
在清單 3 中,我們創建了一個名為 `lang`的變量,并將其值設置為“Groovy”。我們打印了一個 `GString`類型的 `String`,并要求將單詞 “dig” 后面的內容替換為 `${lang}`的值。如果實際運行一下,這段代碼會打印“Uncle man, Uncle man, I dig Groovy.” 一切都不錯,不是嗎?
運行時替換實際上是動態語言的一個通用特性;與其他情況一樣,Groovy 還會更進一步。Groovy 的 `GString`允許您對替換的值調用 _autocall_方法,當開始構建動態文本時,這會產生很多變化。例如,在清單 4 中,我可以對指定的變量按照 `String`對象類型調用一個方法(在本例中是 `length()`方法)。
##### 清單 4\. GString 自動調用
```
lang = "Groovy"
println "I dig any language with ${lang.length()} characters in its name!"
```
清單 4 中的代碼會打印出“I dig any language with 6 characters in its name!”在下一節中,我將向您展示如何使用 Groovy 的自動調用特性在您的模板中啟用一些復雜的特性。
* * *
## Groovy 模板
對模板的使用可以分解為兩個主要的任務:首先,創建模板;其次,提供映射代碼。使用 Groovy 模板框架創建模板與創建 JSP 非常類似,因為您可以重用 JSP 中見過的語法。創建這些模板的關鍵在于對那些運行時要替換的變量的定義。例如,在清單 5 中,我們為創建 `GroovyTestCase`定義了一個模板。
##### 清單 5\. 一個創建 GroovyTestCase 的模板
```
import groovy.util.GroovyTestCase
class <%=test_suite %> extends GroovyTestCase {
<% for(tc in test_cases) {
println "\tvoid ${tc}() { } "
}%>
}
```
清單 5 中的模板就類似于一個 JSP 文件,因為我們使用了 `<%`和 `<%=`語法。然而,由于 Groovy 的靈活性很好,因此您并不局限于使用 JSP 語法。您還可以自由使用 Groovy 中杰出的 `GString`,如清單 6 所示。
##### 清單 6\. GString 的使用
```
<person>
<name first="${p.fname}" last="${p.lname}"/>
</person>
```
在清單 6 中,我創建了一個簡單的模板,它表示一個定義 `person`元素集合的 XML 文檔。您可以看到這個模板期望一個具有 `fname`和 `lname`屬性、名為 `p`的對象。
在 Groovy 中定義模板相當簡單,其實應該就是這樣簡單才對。Groovy 模板并不是火箭科學,它們只不過是一種簡化從模型中分離視圖過程的手段。下一個步驟是編寫運行時的映射代碼。
* * *
## 運行時映射
既然已經定義了一個模板,我們就可以通過在已定義的變量和運行時值之間建立映射來使用這個模板了。與 Groovy 中常見的一樣,有些看來似乎很復雜的東西實際上是非常簡單的。我所需要的是一個 `映射`,其關鍵字是模板中的變量名,鍵值是運行時的值。
例如,如果一個簡單的模板有一個名為 `favlang`的變量,我就需要與 `favlang`鍵值建立一個 `映射`。這個鍵值可以是根據我自己的喜好選擇的任何腳本語言(在本例中,當然是 Groovy)。
在清單 7 中,我們定義了這樣一個簡單的模板,在清單 8 中,我將向您展示對應的映射代碼。
##### 清單 7\. 用來展示映射的簡單代碼
```
My favorite dynamic language is ${favlang}
```
清單 8 顯示了一個簡單的類,它一共做了 5 件事,其中有 2 件是很重要的。您可以說出它們是什么嗎?
##### 清單 8\. 為一個簡單的模板映射值
```
package com.vanward.groovy.tmpl
import groovy.text.Template
import groovy.text.SimpleTemplateEngine
import java.io.File
class SimpleTemplate{
static void main(args) {
fle = new File("simple-txt.tmpl")
binding = ["favlang": "Groovy"]
engine = new SimpleTemplateEngine()
template = engine.createTemplate(fle).make(binding)
println template.toString()
}
}
```
在清單 8 中為這個簡單的模板映射值簡單得令人吃驚。
首先,我創建了一個 `File`實例,它指向模板 `simple-txt.tmpl`。
然后創建了一個 `binding`對象;實際上,這就是一個 `映射`。我將在模板中找到的值 `favlang`映射到 `String`Groovy 上。這種映射是在 Groovy 中使用模板最重要的步驟,或者說在任何具有模板引擎的語言中都是如此。
接下來,我創建了一個 `SimpleTemplateEngine`實例,在 Groovy 中,它恰巧就是模板引擎框架的一個具體實現。然后我將模板(`simple-txt.tmpl`)和 `binding`對象傳遞給這個引擎實例。在清單 8 中,第二個重要的步驟是將模板及其 `binding`對象綁定在一起,這也是使用模板引擎的關鍵所在。從內部來說,框架將在從 `binding`對象中找到的值與對應模板中的名字之間建立映射。
清單 8 中的最后一個步驟是打印進程的輸出信息。正如您可以看到的一樣,創建一個 `binding`對象并提供正確的映射是件輕而易舉的小事,至少在我們這個簡單的例子中是如此。在下一節中,我們將會使用一個更加復雜的例子對 Groovy 模板引擎進行測試。
* * *
## 更復雜的模板
在清單 9 中,我已經創建了一個 `Person`類來表示在 [清單 6](#code6)中定義的 `person`元素。
##### 清單 9\. Groovy 中的 Person 類
```
class Person{
age
fname
lname
String toString(){
return "Age: " + age + " First Name: " + fname + " Last Name: " + lname
}
}
```
在清單 10 中,您可以看到對上面定義的 `Person`類的實例進行映射的代碼。
##### 清單 10\. 在 Person 類與模板之間建立映射
```
import java.io.File
import groovy.text.Template
import groovy.text.SimpleTemplateEngine
class TemplatePerson{
static void main(args) {
pers1 = new Person(age:12, fname:"Sam", lname:"Covery")
fle = new File("person_report.tmpl")
binding = ["p":pers1]
engine = new SimpleTemplateEngine()
template = engine.createTemplate(fle).make(binding)
println template.toString()
}
}
```
上面的代碼看起來很熟悉,不是嗎?實際上,它與 [清單 8](#code8)非常類似,不過增加了一行創建 `pers1`實例的代碼。現在,再次快速查看一下 [清單 6](#code6)中的代碼。您看到模板是如何引用屬性 `fname`和 `lname`的了嗎?我所做的操作是創建一個 `Person`實例,其 `fname`屬性設置為“Sam”,屬性 `lname`設置為“Covery”。
在運行清單 10 中的代碼時,輸出結果是 XML 文件,用來定義 `person`元素,如清單 11 所示。
##### 清單 11\. Person 模板的輸出結果
```
<person>
<name first="Sam" last="Covery"/>
</person>
```
### 映射一個列表
在 [清單 5](#code5)中,我為 `GroovyTestCase`定義了一個模板。現在如果您看一下這個模板,就會注意到這個定義有一些邏輯用于在一個集合上迭代。在清單 12 中,您將看到一些非常類似的代碼,不過這些代碼的邏輯是用來映射一個測試用例 `列表`的。
##### 清單 12\. 映射測試用例列表
```
fle = new File("unit_test.tmpl")
coll = ["testBinding", "testToString", "testAdd"]
binding = ["test_suite":"TemplateTest", "test_cases":coll]
engine = new SimpleTemplateEngine()
template = engine.createTemplate(fle).make(binding)
println template.toString()
```
查看一下 [清單 5](#code5),它顯示了模板期望一個名為“test_cases”的 `列表`—— 在清單 12 中它定義為 `coll`,包含 3 個元素。我簡單地將 `coll`設置為“test_cases”綁定對象中的鍵值,現在代碼就準備好運行了。
現在應該十分清楚了,Groovy 模板非常容易使用。它還可以促進無所不在的 MVC 模式的使用;更重要的是,它們可以通過表示視圖來支持轉換為 MVC 代碼。在下一節中,我將向您展示如何對上一篇文章中的一個例子應用本文中所學到的知識。
* * *
## 使用模板重構之前的例子
在使用 Groovy 編寫 Ant 腳本的專欄中,我曾經編寫了一個簡單的工具,它對類文件產生校驗和報告。如果您還記得,我當時笨拙地使用 `println`語句來產生 XML 文件。盡管我只能接受這段代碼,但它是如此地晦澀,這您只需要看一下清單 13 就會一目了然。
##### 清單 13\. 糟糕的代碼
```
nfile.withPrintWriter{ pwriter |
pwriter.println("<md5report>")
for(f in scanner){
f.eachLine{ line |
pwriter.println("<md5 class='" + f.path + "' value='" + line + "'/>")
}
}
pwriter.println("</md5report>")
}
```
為了幫您回顧一下有關這段內容的記憶,清單 13 中的代碼使用了一些數據,并使用 `PrintWriter`將其寫入一個文件中(`nfile`實例)。注意我是如何將報告的視圖組件(XML)硬編碼在 `println`中的。這種方法的問題是它不夠靈活。之后,如果我需要進行一些修改,就只能進入到 Groovy 腳本的邏輯中進行修改。在更糟糕的情況下,設想一下一個非程序員想要進行一些修改會是什么樣子。Groovy 代碼會受到很大的威脅。
將該腳本中的視圖部分移動到模板中可以使維護更加方便,因為修改模板是任何人都會涉及的一個過程,因此我在此處也會這樣做。
### 定義模板
現在我將開始定義模板了 —— 它看起來更加類似于想要的輸出結果,采用了一些邏輯來循環遍歷一組類。
##### 清單 14\. 為原來的代碼應用模板
```
<md5report>
<% for(clzz in clazzes) {
println "<md5 class=\"${clzz.name}\" value=\"${clzz.value}\"/>"
}%>
</md5report>
```
清單 14 中定義的模板與為 `GroovyTestCase`定義的模板類似,其中包括循環遍歷一個集合的邏輯。還要注意我在此處混合使用了 JSP 和 `GString`的語法。
### 編寫映射代碼
定義好模板之后,下一個步驟是編寫運行時的映射代碼。我需要將原來的寫入文件的邏輯替換為下面的代碼:構建一個 `ChecksumClass`對象集合,然后將這些對象放到 `binding`對象中。
這個模型然后就會變成清單 15 中定義的 `ChecksumClass`。
##### 清單 15\. 在 Groovy 中定義的 CheckSumClass
```
class CheckSumClass{
name
value
String toString(){
return "name " + name + " value " + value
}
}
```
Groovy 中類的定義非常簡單,不是嗎?
### 創建集合
接下來,我需要重構剛才寫入文件的那段代碼 —— 這一次采用一定的邏輯使用 `ChecksumClass`構造一個列表,如清單 16 所示。
##### 清單 16\. 重構代碼創建一個 ChecksumClass 的集合
```
clssez = []
for(f in scanner){
f.eachLine{ line |
iname = formatClassName(bsedir, f.path)
clssez << new CheckSumClass(name:iname, value:line)
}
}
```
清單 16 顯示了使用類 Ruby 的語法將對象添加到 `列表`中是多么簡單 —— 這就是 _奇妙的_groovy。我首先使用 `[]`語法創建 `清單`。然后使用簡短的 `for`循環,后面是一個帶有閉包的迭代器。這個閉包接受每一個 `line`(在本例中是一個校驗和值),并創建一個新定義的 `CheckSumClass`實例(使用 Groovy 的自動生成的構造函數),并將二者添加到集合中。還不錯 —— 這樣編寫起來也很有趣。
### 添加模板映射
我需要做的最后一件事情是添加模板引擎特定的代碼。這段代碼將執行運行時映射,并將對應的格式化后的模板寫入原始的文件中,如清單 17 所示。
##### 清單 17\. 使用模板映射重構原來的代碼
```
fle = new File("report.tmpl")
binding = ["clazzes": clzzez]
engine = new SimpleTemplateEngine()
template = engine.createTemplate(fle).make(binding)
nfile.withPrintWriter{ pwriter |
pwriter.println template.toString()
}
```
現在,清單 17 中的代碼對您來說太陳舊了。我利用了清單 16 中的 `列表`,并將其放入 `binding`對象。然后讀取 `nfile`對象,并將相應的輸出內容從 [清單 14](#code14)中的映射模板寫入文件中。
在將這些內容都放入清單 18 之前,您可能希望返回 [清單 13](#code13)最后看一眼開始時使用的那段蹩腳的代碼。下面是新的代碼,您可以進行比較一下:
##### 清單 18\. 看,新的代碼!
```
/**
*
*/
buildReport(bsedir){
ant = new AntBuilder()
scanner = ant.fileScanner {
fileset(dir:bsedir) {
include(name:"**/*class.md5.txt")
}
}
rdir = bsedir + File.separator + "xml" + File.separator
file = new File(rdir)
if(!file.exists()){
ant.mkdir(dir:rdir)
}
nfile = new File(rdir + File.separator + "checksum.xml")
clssez = []
for(f in scanner){
f.eachLine{ line |
iname = formatClassName(bsedir, f.path)
clssez << new CheckSumClass(name:iname, value:line)
}
}
fle = new File("report.tmpl")
binding = ["clazzes": clzzez]
engine = new SimpleTemplateEngine()
template = engine.createTemplate(fle).make(binding)
nfile.withPrintWriter{ pwriter |
pwriter.println template.toString()
}
}
```
現在,雖然我沒有聲明要編寫非常漂亮的代碼,但是這些代碼當然不會像原來的代碼一樣 _糟糕_了。回顧一下,我所干的事情不過是將一些蹩腳的 `println`替換成 Groovy 的更精巧的模板代碼。(一些熟悉重構的人可能會說我應該使用 _Extract Method_進一步對代碼進行優化。)
* * *
## 結束語
在本月的教程中,我希望已經向您展示了 Groovy 的 _視圖_。更 _明確地_說,當您需要快速開發一些需要視圖的簡單程序時,Groovy 的模板框架是普通 Java 編碼的一種很好的替代品。模板是很好的一種抽象,如果使用正確,它可以極大地降低應用程序的維護成本。
下一個月,我將向您展示如何使用 Groovy 來構建使用 Groovlets 的 Web 應用程序。現在,享受使用 Groovy 的模板開發吧!
* * *
## 下載
| 描述 | 名字 | 大小 |
| --- | --- | --- |
[j-pg02155-source.zip](http://www.ibm.com/developerworks/apps/download/index.jsp?contentid=58276&filename=j-pg02155-source.zip&method=http&locale=zh_CN) | 2.18 KB |
- 實戰 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