# 實戰 Groovy: 用 Groovy 打造服務器端
_用 Groovlet 和 GSP 進行動態服務器端編程_
Groovlet 和 GroovyServer Pages(GSP)框架都是建立在 Java Servlet API 基礎之上。不過,與 Strut 和 JSF 不同,Groovy 的服務器端實現不意味著適用于所有情況。相反,它提供了一種快速而又方便地開發服務器端應用程序的簡化方法。下面請跟隨 Groovy 的鼓吹者 Andrew Glover,聽聽他如何介紹這些框架,并展示它們的應用。
Java 平臺為自己贏得了服務器端應用程序開發的首選平臺的名聲。Servlet 是服務器端 Java 技術的強大支柱,因此有無數的框架是圍繞著 Servlet API 建立 起來的,其中包括 Strut、JavaServer Faces (JSF) 和 Tapestry。您可能已經猜到,Groovy 也是以 Servlet API 為基礎建立起來的框架,不過,這個框架的目的是簡化開發。
Groovlet 和 GroovyServer Pages(GSP)框架的目的是提供一種優雅而又簡單的平臺,將它用于構建復雜程度不高的 Web 應用程序。就像 GroovySql 不是數據庫開發的惟一選擇一樣,Groovlet 框架也_不是_ 像 Strut 那樣具有更豐富功能的框架的替代品。Groovlet 只是 開發人員尋求容易配置和產生工作代碼的快速方法時的一種選擇。
例如,不久前,我需要 —— _快速地_ —— 提供一個 stub 應用程序,以測試像 `xml-rpc` API 這樣的客戶端。顯然可以用一個 servlet 快速編寫出所需要的功能,但是我從沒想過鉆研 Strut,一秒鐘也沒有。我考慮過使用基本的普通 Java Servlet API 編寫 servlet 及其相關的邏輯,但是由于需要盡快地使用這項功能,所以我選擇了使用 Groovlet 快速完成它。
很快您就可看到,這種選擇是顯而易見的。
在深入研究使用 Groovlet 進行編程之前,我想簡單介紹一個在示例代碼中會用到的 Groovy 特性。 在幾個月前的 [alt.lang.jre: 感受 Groovy](/developerworks/cn/java/j-alj08034/index.html) 一文中,我第一次介紹了 `def` 關鍵字。
## 關于本系列
將任何工具添加到開發實踐中的關鍵是了解什么時候使用它和什么時候 不使用它。腳本語言可以為您增加特別強大的工具,但前提是在相關的場景 中正確地使用它。因此,_實戰 Groovy_ 的系列文章專門探討 Groovy 的實際使用,并指導讀者什么時候 以及如何成功地使用這些工具。
## 在腳本中定義函數
在普通 Java 編程中,方法必須存在于類對象中。事實上,所有行為都必須在類的上 下文中定義。不過在 Groovy 中,行為可以在_函數_ 中定義,而函數可以在類定義之外定義 。
這些函數可以直接用名稱引用,并且可以在 Groovy 腳本中定義,這樣非常 有助于它們的重復使用。Groovy 函數需要 `def` 關鍵字, 可以將關鍵字想像為在腳本范圍內可用的全局靜態方法。因為 Groovy 是動態類型的語言,所以 `def` 不需要對參數作任何類型聲明,`def` 也不需要 `return` 語句。
例如,在清單 1 中,我定義了一個簡單的函數,它將輸出一個集合的內容,而不管這個集合是 `list` 還是 `map`。然后我定義 一個 `list`,填充它,并調用我新定義的 `def`。之后,我創建一個 `map`,并對這個集合做了同樣的操作。
##### 清單 1\. 這就是 def!
```
def logCollection(coll){
counter = 0;
coll.each{ x |
println "${++counter} item: ${x}"
}
}
lst = [12, 3, "Andy", 'c']
logCollection(lst)
mp = ["name" : "Groovy", "date" : new Date()]
logCollection(mp)
```
`def` 不需要 `return` 語句,因此如果最后一行產生某個 值,那么這個值由 `def` 返回。例如,在清單 2 中,代碼定義了一個 `def`,它返回傳遞進來的變量的名稱。 我可以編寫它,讓它帶有或者不帶 `return` 語句,得到的結果是相同的。
##### 清單 2\. 在 def 中 return 語句是可選的
```
def getJavaType(val){
val.class.getName()
}
tst = "Test"
println getJavaType(tst)
```
在編寫簡單的腳本時,`def` 關鍵字會非常好用。您很快就會看到,在開發 Groovlet 時,這個關鍵字也會派上用場。
* * *
## Groovlet 和 GSP
使用 Groovlet 和 GSP 的前提條件相當簡單:需要一個 servlet 容器,以及最新、最偉大 版本的 Groovy。這些框架的好處是它們通過一個 web.xml 文件將所選模式的所有 URL 映射到特定的 servlet。因此, 建立 Groovlet 和 GSP 的實現的第一步是定義一個 Web 應用程序上下文,并更新它的相關 web.xml 文件。 這個文件將包括特定的 servlet 類定義以及它們對應的 URL 模式。
我將使用 Apache Jakarta Tomcat,并且已創建了一個名為 _groove_ 的上下文。目錄結構如清單 3 所示:
##### 清單 3\. groove 上下文的目錄列表
```
./groove:
drwxrwxrwx+ 3 aglover users 0 Jan 19 12:14 WEB-INF
./WEB-INF:
-rwxrwxrwx+ 1 aglover users 906 Jan 16 14:37 web.xml
drwxrwxrwx+ 2 aglover users 0 Jan 19 17:12 lib
./WEB-INF/lib:
-rwxrwxrwx+ 1 aglover users 832173 Jan 16 14:28 groovy-1.0-beta-9.jar
-rwxrwxrwx+ 1 aglover users 26337 Jan 16 14:29 asm-1.5.2.jar
```
在 WEB-INF 目錄中要有一個 web.xml 文件,它至少有一個清單 4 中的元素:
##### 清單 4\. 一個完全配置的 web.xml 文件
```
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<servlet>
<servlet-name>GroovyServlet</servlet-name>
<servlet-class>groovy.servlet.GroovyServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>GroovyTemplate</servlet-name>
<servlet-class>groovy.servlet.TemplateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GroovyServlet</servlet-name>
<url-pattern>*.groovy</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>GroovyTemplate</servlet-name>
<url-pattern>*.gsp</url-pattern>
</servlet-mapping>
</web-app>
```
上述 web.xml 文件中的定義聲明了以下內容:所有以 `.groovy` 結尾的請求(如 `http://localhost:8080/groove/hello.groovy`)都將發送給 類 `groovy.servlet.GroovyServlet`, 而所有以 `.gsp` 結尾的請求 都將送給類 `groovy.servlet.TemplateServlet`。
下一步是將兩個 jar 放到 lib 目錄中:groovy 發行版本的 jar(在這里是 groovy-1.0-beta-9.jar)和 對應的 `asm` jar (對于 groovy beta-9 來說是 asm-1.5.2.jar)。
瞧,就是這么簡單 —— 我已經準備好了。
* * *
## Groovlet,請出場
編寫 Groovlet 無疑很簡單,因為 Groovy 只有很少的幾個 對類繼承擴展的要求。使用 Groovlet 不需要擴展 `javax.servlet.http.HttpServlet`、`javax.servlet.GenericServlet` 或者一些華而不實的 `GroovyServlet` 類。事實上,創建 Groovlet 就像創建一個 Groovy 腳本一樣簡單。甚至不必 創建一個類。在清單 5 中,我編寫了一個簡單的 Groovlet,它 做兩件事:打印一些 HTML,然后提供一些關于它所在的容器的信息。
##### 清單 5\. 開始使用 Groovlet
```
println """
<html><head>
<title>Groovlets 101</title>
</head>
<body>
<p>
Welcome to Groovlets 101\. As you can see
this Groovlet is fairly simple.
</p>
<p>
This course is being run on the following servlet container: </br>
${application.getServerInfo()}
</p>
</body>
</html>
"""
```
如果在瀏覽器中觀看這個 Groovy,它看起來與圖 1 所示類似。
##### 圖 1\. 簡單 Groovlet 的輸出

仔細觀察清單 5 中的 Groovlet,會讓您回想起第一次編寫 Groovy 腳本的時候。 首先,沒有 `main` 方法或者類定義,只有一些簡單的代碼。而且,Groovlet 框架隱式地提供實例變量,比如 `ServletRequest`、`ServletResponse`、`ServletContext` 和 `HttpSession`。注意我是如何通過 `application` 變量引用 `ServletContext` 的實例的。如果想獲得 `HttpSession` 的實例,那么就要使用 `session` 變量名。與此類似,可以對 `ServletRequest` 和 `ServletResponse` 分別使用 `request` 和 `response`。
* * *
## 一個診斷 Groovlet
編寫 Groovlet 不僅像創建一個 Groovy 腳本那樣簡單,而且還可以用 `def` 關鍵字定義函數,并在 Groovlet 中直接調用它們。為了展示這一點,我將 創建一個非凡的 Groovlet,它將對 Web 應用程序進行一些診斷。
假設您編寫了一個 Web 應用程序,它被世界上不同的客戶所購買。您有一個 大客戶群,并且不斷發布這個應用程序有一段時間了。從過去的支持問題中,您 注意到許多急切的客戶電話都與錯誤的 JVM 版本和錯誤的對象關系映射(ORM)所導致的問題有關。
您很忙,所以讓我拿出一個解決方案。我用 Groovlet _迅速地_ 創建了一個簡單的 診斷腳本,它將驗證 VM 版本,并試圖創建一個 Hibernate 會話(請參閱[參考資料](#resources))。我首先創建 兩個函數,并在瀏覽器連接腳本時調用它們。 清單 6 定義了這個診斷 Groovlet:
##### 清單 6\. 一個診斷 Groovlet
```
import com.vanward.resource.hibernate.factory.DefaultHibernateSessionFactory
/**
* Tests VM version from environment- note, even 1.5 will
* cause an assertion error.
*/
def testVMVersion(){
println "<h3>JVM Version Check: </h3>"
vers = System.getProperty("java.version")
assert vers.startsWith("1.4"): "JVM must be at least 1.4"
println "<p>JVM version: ${vers} </p>"
}
/**
* Attempts to create an instance of a hibernate session. If this
* works we have a connection to a database; additionally, we
* have a properly configured hibernate instance.
*/
def testHibernate(){
println "<h3>Hibernate Configuration Check: </h3>"
try{
sessFactory = DefaultHibernateSessionFactory.getInstance()
session = sessFactory.getHibernateSession()
assert session != null: "Unable to create hibernate session.
Session was null"
println "<p>Hibernate configuration check was successful</p>"
}catch(Throwable tr){
println """
<p>Unable to create hibernate session. Exception type is: <br/>
<i>${tr.toString()} </i><br/>
</p>
"""
}
}
println """
<html><head>
<title>Diagnostics Check</title></head>
<body>
"""
testVMVersion()
testHibernate()
println """
</body></html>
"""
```
這個 Groovlet 的驗證邏輯非常簡單,但是它可以完成這項工作。只要將 診斷腳本綁定到 web 應用程序即可,當客戶服務臺收到電話時,它們將指點客戶用瀏覽器 訪問 `Diagnostics.groovy` 腳本,并讓這些腳本報告它們的發現。 結果可能看起來像圖 2 這樣。
##### 圖 2\. 診斷 Groovlet 的輸出

* * *
## 那些 GSP 呢?
到目前為止,我主要關注于編寫 Groovlet。不過,正如您將會看到的那樣, 很容易用 Groovy 的 GSP 頁對 Groovlets 框架進行補充,就像 JSP 補充 Servlet API 一樣。
表面上,GSP 看起來很像 JSP,實際上它們不可能有太大的差別,因為 GSP 框架其實就是一個模板引擎。如果不熟悉模板引擎,那么可能需要快速地 回顧一下[上月的文章](/developerworks/cn/java/j-pg02155/index.html)。
雖然 GSP 和 JSP 是根本不同的技術,但是 GSP 是加入表達 Web 應用程序的視圖的很 好候選人,這一點它們是類似的。您可能會想起來,在上月的文章中,有一項促進視圖的技術可以將應用程序的業務邏輯問題與其相應的視圖分離。 如果快速查看一下[清單 6](#code6) 中的診斷 Groovlet,就可以看出 GSP 代碼改進了哪些地方。
是的,Groovlet 有些簡陋,不是嗎?問題在于它混合了應用程序邏輯和大量輸出 HTML 的 `println`。幸運的是,可以通過創建一個簡單的 GSP 來補充這個 Groovlet,從而解決這個問題。
### 示例 GSP
創建 GSP 與創建 Groovlet 一樣容易。GSP 開發的關鍵是認識到 GSP 實質上 是一個模板,因此,它最適合有限的邏輯。我將在清單 7 中創建一個 簡單的 GSP 作為開始:
##### 清單 7\. 一個簡單的 GSP
```
<html>
<head><title>index.gsp</title></head>
<body>
<b><% println "hello gsp" %></b>
<p>
<% wrd = "Groovy"
for (i in wrd){
%>
<h1> <%=i%> <br/>
<%} %>
</p>
</body>
</html>
```
觀察上面的 GSP 可能很容易讓您回想起標準 Groovy 模板開發。 像 JSP 一樣,它使用 `<%`, 但是, 與 Groovlet 框架類似,它允許您訪問常用 servlet 對象,比如 `ServletRequest`、`ServletResponse`、`ServletContext` 和 `HttpSession` 對象。
* * *
## 重構應用程序 ...
在練習編程語言或者平臺發展的時候,重構老的代碼可以學到很多東西。我將重構[一月份專欄](/developerworks/cn/java/j-pg01115.html)中的簡單報告應用程序,那時候您才剛開始學習 GroovySql。
您還記得嗎,我構建了一個快速但不完善的報告應用程序,它可以在組織中有 多次使用。但結果是,它變成了研究公司數據庫活動的相當流行的應用程序。 現在,非技術人員希望可以訪問這個巨大的報告,但是他們不想很費事地在自已的 計算機上安裝 Groovy 來運行它。
我多少預計到了這種情況的發生,解決方案_實際上_ 是顯而易見的:讓報告應用程序支持 Web。很幸運,Groovlet 和 GSP 使重構變成小事一樁。
### 重構報告應用程序
首先,我將處理 [> GroovySql 一文的清單 12](/developerworks/cn/java/j-pg01115.html# code12) 中的簡單應用程序。重構這個應用程序很容易:只要將所有 `println` 替換成用 `setAttribute()` 方法,然后將實例變量放入 `HttpRequest` 對象的邏輯中即可。
下一步是用 `RequestDispatcher` 將 `request` 轉發給 GSP,它會處理報告應用程序的視圖部分。清單 8 定義了新的報告 Groovlet:
##### 清單 8\. 重構后的數據庫報告應用程序
```
import groovy.sql.Sql
/**
* forwards to passed in page
*/
def forward(page, req, res){
dis = req.getRequestDispatcher(page);
dis.forward(req, res);
}
sql = Sql.newInstance("jdbc:mysql://yourserver.anywhere/tiger", "scott",
"tiger", "org.gjt.mm.mysql.Driver")
uptime = null
questions = null
insertnum = null
selectnum = null
updatenum = null
sql.eachRow("show status"){ status |
if(status.variable_name == "Uptime"){
uptime = status[1]
request.setAttribute("uptime", uptime)
}else if (status.variable_name == "Questions"){
questions = status[1]
request.setAttribute("questions", questions)
}
}
request.setAttribute("qpm", Integer.valueOf(questions) /
Integer.valueOf(uptime) )
sql.eachRow("show status like 'Com_%'"){ status |
if(status.variable_name == "Com_insert"){
insertnum = Integer.valueOf(status[1])
}else if (status.variable_name == "Com_select"){
selectnum = Integer.valueOf(status[1])
}else if (status.variable_name == "Com_update"){
updatenum = Integer.valueOf(status[1])
}
}
request.setAttribute("qinsert", 100 * (insertnum / Integer.valueOf(uptime)))
request.setAttribute("qselect", 100 * (selectnum / Integer.valueOf(uptime)))
request.setAttribute("qupdate", 100 * (updatenum / Integer.valueOf(uptime)))
forward("mysqlreport.gsp", request, response)
```
清單 8 中的代碼應當是您相當熟悉的。我只是將以前應用程序中的所有 `println` 替換掉,并添加了 `forward` 函數來處理報告的視圖部分。
### 添加視圖部分
下一步是創建 GSP 來處理報告應用程序的視圖。 因為我是工程師而不是一個藝術家,所以我的視圖是相當 簡單的 —— 一些 HTML 加上一個表,如清單 9 所示:
##### 清單 9\. 報告的視圖部分
```
<html><head>
<title>MySql Health Report</title>
</head>
<body>
<table>
<tr>
<td>Database Uptime:</td><td><% println
"${request.getAttribute("uptime")}" %></td>
</tr>
<tr>
<td>Number of Queries:</td><td><% println
"${request.getAttribute("questions")}" %></td>
</tr>
<tr>
<td>Queries per Minute =</td><td><% println
"${request.getAttribute("qpm")}" %></td>
</tr>
<tr>
<td>% Queries Inserts =</td><td><% println
"${request.getAttribute("qinsert")}" %></td>
</tr>
<tr>
<td>% Queries Selects =</td><td><% println
"${request.getAttribute("qselect")}" %></td>
</tr>
<tr>
<td>% Queries Updates =</td><td><% println
"${request.getAttribute("qupdate")}" %></td>
</tr>
</table>
</body>
</html>
```
運行新的報告應當生成如圖 3 所示的輸出,數字會有變化。
##### 圖 3\. 重構后的報告應用程序的輸出

* * *
## 結束語
如您所見,當所需要的功能相當簡單并且需要盡快完成時,Groovlet 和 GSP 是進行服務器端開發的當然之選。 這兩個框架都特別靈活,并且其代碼到視圖的轉化時間事實上是無可匹敵的。
不過,需要強調的是,Groovlet 不是 Strut 的替代品。GSP 框架不是_直接_ 在速度上與其他產品競爭。GroovySql 不是 Hibernate 的替代品。而 Groovy 也不是 Java 語言的替代品。
無論如何,這些技術是補充,在大多數情況下,Groovy 是快速開發的更簡單的一種選擇。 就像 GroovySql 是直接使用 JDBC 的替代方法一樣,Groovlet 和 GSP 實際上是直接使用 Servlet API 的替代品。
下個月,我將探討 GroovyMarkup 的奇妙世界。
- 實戰 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