精通 Grails: 構(gòu)建您的第一個 Grails 應(yīng)用程序(轉(zhuǎn)自developmentwork 中國)
Java™ 程序員不需要放棄自己喜愛的語言和已有的開發(fā)基礎(chǔ)設(shè)施就可以采納一種新型的 Web 開發(fā)框架。在這個新推出的每月一期的 精通 Grails 系列的第一期中,Java 專家 Scott Davis 介紹了 Grails,并演示了如何構(gòu)建您的第一個 Grails 應(yīng)用程序。
請允許我借助另一種開源 Web 開發(fā)框架 —— Ruby on Rails —— 來介紹 Grails。Rails 一發(fā)布就迷住了開發(fā)人員。Rails 的腳手架功能使您可以用以前所需時間的一小部分完成一個新的項目。支持 Rails 的約定優(yōu)于配置(convention over configuration)思想意味著,應(yīng)用程序可以根據(jù)常識性的命名模式自動進(jìn)行組裝(auto-wire),而不必借助繁雜的、容易出錯的 XML 配置文件。Ruby 的元編程功能使對象可以神奇地在運(yùn)行時繼承所需的方法和字段,而不會擾亂源代碼。
Rails 配得上它所受到的贊美和推崇(現(xiàn)在仍然如此),但是它使 Java 開發(fā)人員面臨困難的選擇。您會因為一個新平臺的承諾而放棄自己熟悉的 Java 平臺嗎?如何處理已有的 Java 代碼、已有的生產(chǎn)服務(wù)器和經(jīng)驗豐富的 Java 開發(fā)人員?
![]() |
|
Grails 為您提供 Rails 風(fēng)格的開發(fā)體驗,同時以可靠的 Java 技術(shù)作為堅強(qiáng)后盾。但是 Grails 不僅僅是 Rails 通往 Java 平臺的簡單入口。Grails 吸取了 Rails 的經(jīng)驗,并將它們與現(xiàn)代 Java 開發(fā)的意識相結(jié)合。可以認(rèn)為 Grails 是受 Rails 啟發(fā),而不是由 Rails 轉(zhuǎn)化而來。
作為 Grails 入門 系列的開篇,本文介紹 Grails 框架,展示它的安裝方法,遍覽如何構(gòu)建第一個 Grails 應(yīng)用程序:介紹本系列后續(xù)文章的內(nèi)容。
就像 Rails 與 Ruby 編程語言聯(lián)系非常緊密一樣,Grails 也離不開 Groovy(請參閱 參考資料)。Groovy 是一種動態(tài)語言,它在 JVM 上運(yùn)行,并且與 Java 語言無縫集成。如果閱讀了 developerWorks 上的大型 實戰(zhàn) Groovy 系列,那么您已經(jīng)了解了這種語言的威力。如果沒有,也不必?fù)?dān)心 — 在學(xué)習(xí) Grails 的過程中,您將了解到很多關(guān)于 Groovy 的知識。Groovy 應(yīng)該不難學(xué),因為它是特意為 Java 開發(fā)人員而設(shè)計的。
例如,Groovy 可以大大減少 Java 代碼的數(shù)量。在 Groovy 中,不再需要為字段編寫 getter 和 setter 方法,因為 Groovy 會自動提供它們。不再需要編寫 for Iterator i = list.iterator()
來循環(huán)遍歷一系列的項;list.each
可以做相同的事情,而且看上去更簡潔,表達(dá)更清晰。簡言之,Groovy 就是 21 世紀(jì)的 Java 語言。
如果 Java 開發(fā)人員只有重新編寫整個應(yīng)用程序才能利用 Groovy,那么 Groovy 對他們就沒有多大的吸引力了。令人高興的是,Groovy 可以無縫地與已有的代碼庫集成。Groovy 不會替代 Java 語言 — 它只是提供了增強(qiáng)。您可以很快地掌握 Groovy,因為說到底,Groovy 代碼就是 Java 代碼。這兩種語言是如此兼容,甚至可以將一個 .java 文件重命名為一個 .groovy 文件 — 例如,將 Person.java 改為 Person.groovy — 從而得到一個有效的(可執(zhí)行的)Groovy 文件(雖然這個 Groovy 文件并沒有用到 Groovy 提供的任何語法)。
Groovy 與 Java 語言的深度兼容意味著 Grails 不需要重新創(chuàng)造內(nèi)部使用的關(guān)鍵技術(shù)。相反,您可以以 Groovy 的方式查看熟悉的 Java 庫。Groovy 封裝了 JUnit TestCase
并以 GroovyTestCase
形式提供。Grails 通過 GANT 對 Ant 構(gòu)建進(jìn)行了調(diào)整,GANT 是 Ant 的一個純 Groovy 實現(xiàn)。Grails 將 Hibernate 包裝在一個小小的 Groovy facade 中,并稱之為 GORM — Grails Object/Relational Mapper。Grails 使您在利用已有的 Java 經(jīng)驗的同時,還可以利用最新的 Web 開發(fā)實踐,以上只是其中的三個例子。
不過,要想全面地鑒賞 Grails,還需要親身體驗一下。現(xiàn)在,讓我們來安裝 Grails,并創(chuàng)建第一個 Web 應(yīng)用程序。
![]() ![]() |
![]()
|
運(yùn)行 Grails 應(yīng)用程序所需的一切都在一個 ZIP 文件中。所有的依賴庫 — 例如 Groovy、Spring 和 Hibernate — 都已經(jīng)在那里,隨時可以使用。要安裝 Grails:
- 從 Grails 站點(diǎn)(見 參考資料)下載并解壓 grails.zip。
- 創(chuàng)建一個
GRAILS_HOME
環(huán)境變量。 - 將 $GRAILS_HOME/bin 添加到
PATH
中。
您的確 需要安裝一個 JDK(Grails 是不錯,但是還沒有好到 那種程度)。Grails 1.0 可在 Java 1.4、1.5 和 1.6 上運(yùn)行。如果不知道已經(jīng)安裝了哪個版本,可以在命令行提示符下輸入 java -version
。必要時,下載并安裝一個與 Grails 兼容的 JDK(見 參考資料)。
完成安裝步驟后,輸入 grails -version
以進(jìn)行檢查。如果看到以下友好信息,則說明一切都得到正確配置:
Welcome to Grails 1.0 - http://grails.org/ Licensed under Apache Standard License 2.0 Grails home is set to: /opt/grails |
![]() |
|
有趣的是,不需要單獨(dú)安裝 Web 服務(wù)器就可以運(yùn)行 Grails 應(yīng)用程序。 Grails 內(nèi)置了 Jetty servlet 容器。只需輸入 grails run-app
,就可以使應(yīng)用程序在 Jetty 容器(見 參考資料)中運(yùn)行,而不必執(zhí)行常見的部署過程。在已有的生產(chǎn)服務(wù)器上運(yùn)行 Grails 應(yīng)用程序也沒有問題。通過輸入 grails war
創(chuàng)建一個標(biāo)準(zhǔn)文件,然后可以將其部署到 Tomcat、JBoss、Geronimo、WebSphere®,或者任何其他遵從 Java EE 2.4 的 servlet 容器。
您也不需要單獨(dú)安裝數(shù)據(jù)庫。Grails 附帶了 HSQLDB(見 參考資料),它是一個純 Java 數(shù)據(jù)庫。通過提供一個隨時可用的數(shù)據(jù)庫可以立即提高生產(chǎn)率。由于有了 Hibernate 和 GORM,使用其他數(shù)據(jù)庫(例如 MySQL、PostgreSQL、Oracle Database 或 DB2)也很簡單。如果有一個 JDBC driver JAR 再加上通常的連接設(shè)置,只需改變一下 DataSource.groovy,就可以立即使用您自己的數(shù)據(jù)庫。
![]() ![]() |
![]()
|
我經(jīng)常旅行 — 一年至少 40 趟。我發(fā)現(xiàn),日程表可以很好地告訴我何時 需要達(dá)到某個地方,但是不能顯示那個地方在哪里。而在線地圖剛好相反:它們可以解決地點(diǎn)問題,但不能解決時間問題。所以,在本文和本系列接下來的兩篇文章中,您將構(gòu)建一個定制的 Grails 應(yīng)用程序,在計劃旅程時,這個應(yīng)用程序既可以用于解決時間問題,又可以用于解決地點(diǎn)問題。
![]() |
|
首先,在一個空白目錄下,輸入 grails create-app trip-planner
。稍后,可以看到一個名為 trip-planner 的目錄。同 Maven、Rails 和 AppFuse 一樣,Grails 會建立一個標(biāo)準(zhǔn)的目錄結(jié)構(gòu)。如果您覺得這個目錄結(jié)構(gòu)限制了您,并且只有精心設(shè)計自己的定制目錄樹才能使用一個框架,那么這樣使用 Grails 不會有多大的樂趣。約定優(yōu)于配置中的約定 部分使您可以擁有任何 Grails 應(yīng)用程序,并立即知道各個部分之間的聯(lián)系。
進(jìn)入 trip-planner 目錄,并輸入 grails create-domain-class Trip
。如果一切順利,將得到兩個新的文件:grails-app/domain/Trip.groovy 和 grails-app/test/integration/TripTests.groovy。在后面的文章中,我將談到測試。目前,我們主要關(guān)注域類。一開始,域類看上去如清單 1 所示:
清單 1. Grails 生成的域類
class Trip{ } |
看上去沒什么內(nèi)容,對嗎?接下來讓我們來完善它。為 Trip
添加一些字段,如清單 2 所示:
清單 2. 添加字段后的
Trip
類
class Trip { String name String city Date startDate Date endDate String purpose String notes } |
如前所述,這里不需要創(chuàng)建 getter 和 setter 方法:Groovy 會動態(tài)地生成它們。Trip
還有很多新的、有用的動態(tài)方法,這些方法的名稱非常易用理解:
Trip.save()
將數(shù)據(jù)保存 到 HSQLDB 數(shù)據(jù)庫中的Trip
表中。Trip.delete()
從Trip
表中刪除 數(shù)據(jù)。Trip.list()
返回一個Trip
列表。Trip.get()
返回一個Trip
。
所有這些方法都已經(jīng)存在,您在需要的時候就可以使用它們。注意,Trip
并沒有擴(kuò)展某個父類或者實現(xiàn)某個接口。由于 Groovy 的元編程功能,那些方法只是出現(xiàn)在適當(dāng)類中的適當(dāng)位置(只有 grails-app/domain 目錄中的類才擁有這些與持久性相關(guān)的方法)。
創(chuàng)建域類只是成功的一半。每個模型都還需要一個良好的控制器和一些視圖(我假設(shè)您熟悉 Model-View-Controller 模式;請參閱 參考資料)。輸入 grails generate-all Trip
,以構(gòu)建一個 grails-app/controllers/TripController.groovy 類,并在 grails-app/views/Trip 中生成一組匹配的 Groovy Server Page(GSP)。對于控制器中的每個 list
動作,都有一個相應(yīng)的 list.gsp 文件。create
動作則對應(yīng)于一個 create.gsp 文件。從這里可以看出約定優(yōu)于配置的優(yōu)點(diǎn):無需 XML 文件就可以匹配這些元素。每個域類根據(jù)名稱與一個控制器配對。控制器中的每個動作也是根據(jù)名稱與一個視圖配對。如果您愿意,也可以繞開這種基于名稱的配置,但是大多數(shù)時候只需遵循約定,應(yīng)用程序自然就可以運(yùn)行。
看看清單 3 所示的 grails-app/controller/TripController.groovy:
清單 3.
TripController
class TripController { ... def list = { if(!params.max) params.max = 10 [ tripList: Trip.list( params ) ] } ... } |
Java 開發(fā)人員首先會注意到的是,這么少的代碼可以實現(xiàn)多少功能。以 list
動作為例。起重要作用的是最后一行。Grails 將返回一個 hashmap,其中只有一個名為 tripList
的元素。(Groovy 方法的最后一行是一個隱式的 return 語句。如果您愿意,也可以手動地輸入單詞 return
)。tripList
元素是 Trip
對象的一個 ArrayList
,Trip
對象是通過 Trip.list()
方法從數(shù)據(jù)庫中拉出的。通常該方法將返回表中的全部記錄。它上面的一行代碼表示 “如果 URL 中提供了一個 max 參數(shù),那么使用它來限制返回的 Trip
的數(shù)量。否則,將 Trip
的數(shù)量限制為 10”。URL http://localhost:8080/trip-planner/trip/list 將調(diào)用這個動作。例如,http://localhost:8080/trip-planner/trip/list?max=3 顯示 3 個 trip,而不是通常的 10 個。如果有更多的 trip 要顯示,Grails 會自動創(chuàng)建上一頁和下一頁的分頁鏈接。
那么,如何使用這個 hashmap?看看 grails-app/views/list.gsp,如清單 4 所示:
清單 4. list.gsp
<g:each in="${tripList}" status="i" var="trip"> <tr class="${(i % 2) == 0 ? 'odd' : 'even'}"> <td> <g:link action="show" id="${trip.id}">${trip.id?.encodeAsHTML()}</g:link> </td> </tr> </g:each> |
list.gsp 主要是一些老式 HTML 加上少量 GroovyTagLib。以 g:
為前綴的就是 GroovyTag。在清單 4 中,g:each
遍歷 tripList ArrayList
中的每個 Trip
,并構(gòu)建一個格式良好的 HTML 表格。
對控制器的理解可以歸結(jié)為三個 R:return、redirect 和 render。有些動作利用隱式的 return 語句將數(shù)據(jù)返回到具有相同名稱的 GSP 頁面。有些動作進(jìn)行重定向。例如,如果 URL 中未指定動作,則將調(diào)用 index
:
def index = { redirect(action:list,params:params) } |
在此,TripController
重定向到 list
動作,同時傳遞 params
hashmap 中的所有的參數(shù)(或 QueryString
)。
最后,save
動作(見清單 5)并沒有相應(yīng)的 save.gsp 頁面。如果記錄被成功地保存到數(shù)據(jù)庫中,那么該動作會重定向到 show
動作頁面。否則,它呈現(xiàn) create.gsp 頁面,以便顯示錯誤,并讓您重試。
清單 5.
save
動作
def save = { def trip = new Trip(params) if(!trip.hasErrors() && trip.save()) { flash.message = "Trip ${trip.id} created" redirect(action:show,id:trip.id) } else { render(view:'create',model:[trip:trip]) } } |
在此,我們不詳細(xì)討論 Grails 是如何工作的,而是看看它的實際效果。
![]() ![]() |
![]()
|
在命令行輸入 grails run-app
。控制臺在快速顯示一批 Log4j 消息之后,將顯示如下所示的消息:
Server running. Browse to http://localhost:8080/trip-planner |
如果端口 8080 上已經(jīng)有一個服務(wù)器在運(yùn)行,那么將顯示一條核心轉(zhuǎn)儲信息:
Server failed to start: java.net.BindException: Address already in use |
可以通過兩種方法輕松更改 Jetty 所使用的端口。可以通過輸入 grails -Dserver.port=9090 run-app
臨時進(jìn)行更改。如果要使更改持久,可以從 $GRAILS_HOME/scripts/Init.groovy 中找出以 serverPort
開頭的那一行,并更改值:
serverPort = System.getProperty('server.port') ? System.getProperty('server.port').toInteger() : 9090 |
使 Grails 在您選擇的端口上運(yùn)行之后,在 Web 瀏覽器中輸入 URL。應(yīng)該可以看到一個歡迎屏幕,其中列出所有的控制器,如圖 1 所示:
圖 1. Grails 應(yīng)用程序的歡迎屏幕

單擊 TripController 鏈接。您有一個完整的 CRUD(創(chuàng)建、讀取、更新、刪除)應(yīng)用程序可以使用。
使用圖 2 所示的頁面創(chuàng)建新的 trip:
圖 2. Create Trip 頁面

使用圖 3 所示的頁面編輯 trip:
圖 3. Trip List 頁面

準(zhǔn)備和運(yùn)行這個應(yīng)用程序要花多長時間?需要多少代碼?下面就是答案:
- 按下 Ctrl-C,關(guān)閉 Grails。
- 輸入
grails stats
。
屏幕上將顯示輸出:
+----------------------+-------+-------+ | Name | Files | LOC | +----------------------+-------+-------+ | Controllers | 1 | 66 | | Domain Classes | 1 | 8 | | Integration Tests | 1 | 4 | +----------------------+-------+-------+ | Totals | 3 | 78 | +----------------------+-------+-------+ |
只需不到 100 行代碼,就可以實現(xiàn)應(yīng)用程序的所有功能。看起來還不錯。不過,最后我還要再展示一個竅門。
生成控制器和視圖是一項很好的學(xué)習(xí)體驗,而磁盤上的物理文件則有助于說明各個部分是如何連接在一起的。不過在此需要做一件事:刪除 TripController
類中的內(nèi)容,并用下面的內(nèi)容替代:
class TripController{ def scaffold = Trip } |
這行代碼告訴 Grails 像對待前一個控制器一樣,在運(yùn)行時在內(nèi)存中動態(tài)地生成所有那些 list
、save
和 edit
動作。僅僅 3 行代碼就可以產(chǎn)生和 66 行代碼一樣的行為。
再次輸入 grails run-app
。是的 — 所有數(shù)據(jù)都沒有了。不必?fù)?dān)心。按下 Ctrl-C 關(guān)閉 Grails。這一次,輸入 grails prod run-app
。現(xiàn)在處于生產(chǎn)模式下,這意味著在服務(wù)器重新啟動之前,數(shù)據(jù)已被保存。通過一連串的單擊進(jìn)入 TripController
,保存一些記錄。應(yīng)用程序的行為應(yīng)該沒有什么不同。您已經(jīng)知道,在瀏覽器中看到的一切,是由 15 行代碼驅(qū)動的,可知 Grails 的威力有多大。
![]() ![]() |
![]()
|
希望您對 Grails 的初次體驗感到滿意。小小一個包中,竟包含了令人驚訝的威力,而您只是看到冰山一角。這個框架的安裝非常簡單,只需解壓一個文件。通過輸入幾行命令,就可以從頭創(chuàng)建一個應(yīng)用程序。希望這次簡單的介紹能勾起您對 Grails 的更大興趣。當(dāng)然,本文也為您打好了一個基礎(chǔ),您可以擴(kuò)展這個例子,嘗試各種新的、有趣的方面。
在下個月的文章中,您將專門花一些時間來關(guān)注 GORM。您將把日期保存到一個 MySQL 數(shù)據(jù)庫中,進(jìn)行某些數(shù)據(jù)驗證,并設(shè)置一個一對多的關(guān)系。不必添加很多代碼,就可以明顯增強(qiáng) trip-planner 應(yīng)用程序的功能。
到那時,好好享受使用 Groovy 和 Grails 的樂趣吧。您對 Web 開發(fā)的看法將徹底改變。
![]() |
||
|
![]() |
Scott Davis 是國際知名作家、演講家、軟件開發(fā)人員。他出版的書籍有 Groovy Recipes: Greasing the Wheels of Java、GIS for Web Developers: Adding Where to Your Application、The Google Maps API 和 JBoss At Work。 |