概述
Play 框架是一個(gè)完整的 Web 應(yīng)用開(kāi)發(fā)框架,覆蓋了 Web 應(yīng)用開(kāi)發(fā)的各個(gè)方面。Play 框架在設(shè)計(jì)的時(shí)候借鑒了流行的
Ruby on Rails 和 Grails 等框架,又有自己獨(dú)有的優(yōu)勢(shì)。使用 Play 框架可以方便和高效的開(kāi)發(fā)出 Java Web
應(yīng)用。通過(guò) Play 框架提供的命令行工具,可以快速的創(chuàng)建出一個(gè) Web 應(yīng)用的基本骨架。它的 Java
代碼動(dòng)態(tài)編譯機(jī)制,使得修改代碼之后,不需要重啟服務(wù)器就可以直接看到修改之后的結(jié)果,調(diào)試起來(lái)非常方便。它使用 JPA
規(guī)范來(lái)完成領(lǐng)域?qū)ο蟮某志没梢院芊奖愕氖褂貌煌年P(guān)系數(shù)據(jù)庫(kù)作為后臺(tái)存儲(chǔ)。使用 Play 框架可以很容易的構(gòu)建使用 REST
架構(gòu)風(fēng)格的應(yīng)用。它使用 Groovy 作為視圖層模板使用的表達(dá)式語(yǔ)言。模板之間的繼承機(jī)制也可以避免代碼的重復(fù)??偟膩?lái)說(shuō),Play
框架非常適合快速 Web 應(yīng)用開(kāi)發(fā)。
Play 框架采用經(jīng)典的 MVC 架構(gòu),把 Web 應(yīng)用分成模型層、控制層和視圖層三個(gè)層次。每個(gè)層次對(duì)應(yīng)的文件被存放在不同的目錄下面,方便組織和管理。使用 Play 框架的 Web 應(yīng)用具有相同的目錄結(jié)構(gòu),如 圖 1 所示。
圖 1. 使用 Play 框架的 Web 應(yīng)用的目錄結(jié)構(gòu)
如 圖 1 所示,應(yīng)用自身的文件被放在 app
目錄下面,三個(gè)子目錄分別存放的是 MVC 模式的三個(gè)層次的內(nèi)容。其中 models
和 controllers
目錄下面是 Java 源文件,而 views
目錄下面則是視圖層使用的模板文件。conf
目錄下面存放的是應(yīng)用的配置文件、HTTP 路由文件和國(guó)際化所需的消息文件。public
目錄則是存放 Web 應(yīng)用的靜態(tài)文件,包括 JavaScript、CSS 和圖像文件等。lib
目錄存放所需的額外的 Java 庫(kù)。test
目錄存放的是測(cè)試結(jié)果。
開(kāi)發(fā)環(huán)境
本文中使用的 Play 框架的版本是 1.0.3.1,使用的集成開(kāi)發(fā)環(huán)境是 Eclipse 3.6, 使用 Dojo 作為 JavaScript 框架。在 Play 框架官方網(wǎng)站(見(jiàn) 參考資料)下載 Play 框架的壓縮包之后,解壓到某個(gè)目錄,并把該目錄下面的 bin
目錄添加到環(huán)境變量中。接著啟動(dòng)一個(gè)命令窗口,運(yùn)行 play new developers_notebook
就可以創(chuàng)建出一個(gè)新的名為 developers_notebook
的 Web 項(xiàng)目。在項(xiàng)目目錄的父目錄下面,運(yùn)行 play eclipsify developers_notebook
就可以創(chuàng)建出來(lái) Eclipse 工程。通過(guò) Eclipse 導(dǎo)入此工程就可以在 Eclipse 里面進(jìn)行開(kāi)發(fā)了。Play 框架的 support
目錄下的 eclipse
目錄下有個(gè)名為 org.playframework.playclipse
的 Eclipse 插件,將此插件復(fù)制到 Eclipse 的 plugins
目錄就可以安裝。運(yùn)行 play run
就可以運(yùn)行此 Web 應(yīng)用,訪問(wèn) http://localhost:9000
就可以看到。每次在 Eclipse 里面修改了代碼之后,不需要重新啟動(dòng)應(yīng)用,只需要刷新頁(yè)面就能看到更新之后的結(jié)果。這是 Play 框架的一個(gè)非常方便的特性。
本文中的示例應(yīng)用稱為“開(kāi)發(fā)人員記事本”。開(kāi)發(fā)人員可以用它來(lái)記錄開(kāi)發(fā)過(guò)程中的一些注意事項(xiàng)。下面首先介紹 Play 框架中的模型層。
回頁(yè)首
模型層
模型層包含的是 Web 應(yīng)用中的領(lǐng)域?qū)ο?。Play 框架推薦的實(shí)踐是模型層的對(duì)象不應(yīng)該是僅包含 getter/setter
方法的簡(jiǎn)單 Java Beans,而應(yīng)該有自己的業(yè)務(wù)邏輯。Play 框架中應(yīng)用的模型層類(lèi)可以是任何的 Java 類(lèi)。與一般的 Java
Beans 不同的是,模型層類(lèi)使用聲明為 public
的域作為對(duì)象的屬性。Play 框架會(huì)自動(dòng)生成相應(yīng)的 getter/setter 方法。這樣可以使得代碼更加簡(jiǎn)潔。開(kāi)發(fā)人員也可以提供自己的 getter/setter 方法實(shí)現(xiàn)。
領(lǐng)域?qū)ο蟪志没?/span>
領(lǐng)域?qū)ο蟮膶?shí)例一般需要持久化下來(lái)。最常見(jiàn)的持久化方式就是使用關(guān)系數(shù)據(jù)庫(kù)。Play 框架使用 JPA
規(guī)范來(lái)進(jìn)行領(lǐng)域?qū)ο蟮某志没>唧w的后臺(tái)實(shí)現(xiàn)使用的是 Hibernate。開(kāi)發(fā)人員只需要使用 JPA
規(guī)范定義的標(biāo)注,就可以聲明領(lǐng)域的持久化行為。比較好的做法是將領(lǐng)域?qū)ο箢?lèi)繼承自 Play 框架提供的 play.db.jpa.Model
類(lèi)。play.db.jpa.Model
類(lèi)提供了一個(gè)域 id
作為對(duì)象的標(biāo)識(shí)符,也是對(duì)應(yīng)的數(shù)據(jù)庫(kù)表中的主鍵。play.db.jpa.JPASupport
類(lèi)是 play.db.jpa.Model
的父類(lèi),提供了一些實(shí)用方法用來(lái)完成從領(lǐng)域?qū)ο蟮綌?shù)據(jù)庫(kù)之間的映射。表 1 中列出了一些重要的方法,包括常用的增刪改查操作。
表 1. play.db.jpa.JPASupport API 說(shuō)明
方法 | 說(shuō)明 |
create(type, name, params)
|
用來(lái)創(chuàng)建領(lǐng)域?qū)ο箢?lèi)的一個(gè)實(shí)例。參數(shù) type 表示的是領(lǐng)域?qū)ο箢?lèi),類(lèi)型是 java.lang.Class ;name 表示的是領(lǐng)域?qū)ο箢?lèi)的名稱;params 表示的是一個(gè)包含了實(shí)例中屬性值的類(lèi)型為 java.util.Map<java.lang.String,java.lang.String[]> 的哈希表。 |
edit(obj, name, params)
|
用來(lái)編輯領(lǐng)域?qū)ο箢?lèi)的一個(gè)實(shí)例。參數(shù) obj 表示的是領(lǐng)域?qū)ο髮?shí)例;參數(shù) name 和 params 的含義與 create() 方法的相同。 |
delete()
|
用來(lái)刪除單個(gè)領(lǐng)域?qū)ο箢?lèi)的實(shí)例。 |
delete(query, params)
|
用來(lái)刪除多個(gè)領(lǐng)域?qū)ο箢?lèi)的實(shí)例。參數(shù) query 表示的是檢索待刪除實(shí)例的查詢,而 params 表示的是查詢所使用的參數(shù)。 |
deleteAll()
|
用來(lái)刪除領(lǐng)域?qū)ο箢?lèi)的所有實(shí)例。 |
find(query, params)
|
用來(lái)查找領(lǐng)域?qū)ο箢?lèi)的實(shí)例。參數(shù) query 表示的是查找時(shí)所用的查詢,而 params 表示的是查詢所使用的參數(shù)。 |
findAll()
|
用來(lái)查找領(lǐng)域?qū)ο蟮乃袑?shí)例。 |
findById(id)
|
用來(lái)根據(jù)標(biāo)識(shí)符查找領(lǐng)域?qū)ο蟮膶?shí)例。 |
count(query, params)
|
用來(lái)計(jì)算某個(gè)查詢結(jié)果中包含的領(lǐng)域?qū)ο蟮膶?shí)例數(shù)。參數(shù) query 和 params 的含義與 find() 方法相同。 |
save()
|
用來(lái)保存該領(lǐng)域?qū)ο髮?shí)例到數(shù)據(jù)庫(kù)中。 |
all()
|
用來(lái)查找系統(tǒng)中的全部領(lǐng)域?qū)ο蟮膶?shí)例。 |
表 1 中列出的方法中,find()
和 all()
方法的返回值是 play.db.jpa.JPASupport.JPAQuery
類(lèi)的實(shí)例,表示一個(gè)領(lǐng)域?qū)ο髮?shí)例的查詢結(jié)果。對(duì)于此查詢結(jié)果,可以進(jìn)行進(jìn)一步的操作。具體的操作,如 表 2 所示。
表 2. play.db.jpa.JPASupport.JPAQuery API 說(shuō)明
方法 | 說(shuō)明 |
bind(name, param)
|
用來(lái)綁定一個(gè)參數(shù)的實(shí)際值到查詢上。在查詢語(yǔ)句中可以使用形式參數(shù)作為占位符,參數(shù)的實(shí)際值可以通過(guò)此方法來(lái)指定。 |
fetch()
|
用來(lái)獲取此查詢的所有記錄。 |
fetch(max)
|
用來(lái)獲取此查詢的前面 max 條記錄。 |
fetch(page, length)
|
用來(lái)對(duì)查詢結(jié)果進(jìn)行分頁(yè)。參數(shù) page 表示當(dāng)前的頁(yè)數(shù),從 1 開(kāi)始;length 表示每頁(yè)的記錄數(shù)。 |
first()
|
用來(lái)返回查詢結(jié)果中的第一條記錄 |
from(position)
|
用來(lái)設(shè)置查詢結(jié)果中處理的起始位置。參數(shù) position 表示起始位置的序號(hào)。該方法的返回結(jié)果是一個(gè)新的 play.db.jpa.JPASupport.JPAQuery 對(duì)象。 |
使用 表 2 中給出的方法,就可以在領(lǐng)域?qū)ο箢?lèi)中添加一些非常實(shí)用的方法,而不需要把這些方法添加到額外的服務(wù)層中。在示例應(yīng)用中,Note
這個(gè)領(lǐng)域?qū)ο箢?lèi)表示的是用戶添加的記錄。代碼清單 1 中給出了 Note
類(lèi)中的一些實(shí)用方法。
清單 1. 領(lǐng)域?qū)ο箢?lèi)中的實(shí)用方法
// 創(chuàng)建新的領(lǐng)域?qū)ο?Note 的實(shí)例,edit() 方法的使用與 create() 類(lèi)似
Map<String, String[]> params = new HashMap<String, String[]>();
params.put("note.title", new String[] {"My note"});
params.put("note.content", new String[] {"My note's content"});
Note.create(Note.class, "note", params).save();
// 使用 find() 來(lái)進(jìn)行查找
List<Note> notes = Note.find("byTitle", "My note").fetch();
// 使用 findById() 來(lái)查找單個(gè)實(shí)例
Note note1 = Note.findById(1);
// 使用 delete() 來(lái)刪除對(duì)象實(shí)例
Note.delete("byTitle", "My note");
// 返回查詢結(jié)果中的第 2 到第 11 條記錄。
Note.find("byTitle", "My note").from(1).fetch(10);
|
在介紹完 Play 框架的模型層之后,下面介紹控制層。
回頁(yè)首
控制層
Play 框架中的控制層是模型層和視圖層之間的橋梁??刂茖迂?fù)責(zé)接收 HTTP
請(qǐng)求并返回相應(yīng)的響應(yīng)。一般來(lái)說(shuō),控制層的典型實(shí)現(xiàn)是接收到 HTTP
請(qǐng)求之后,從請(qǐng)求中獲取一些參數(shù),再調(diào)用服務(wù)層對(duì)應(yīng)的處理方法。服務(wù)層的方法會(huì)對(duì)領(lǐng)域?qū)ο筮M(jìn)行操作,完成具體的業(yè)務(wù)邏輯。最后,某種格式的響應(yīng)被返回給請(qǐng)
求者,如 HTML 頁(yè)面、JSON 數(shù)據(jù)和 XML 數(shù)據(jù)等。Play 框架的控制層實(shí)現(xiàn)使得完成這樣的典型場(chǎng)景變得非常簡(jiǎn)單。
Play 框架中的每個(gè)控制器都是一個(gè)普通的 Java 類(lèi),繼承自 play.mvc.Controller
類(lèi),在包 controllers
中。控制器類(lèi)中的每個(gè)公開(kāi)的靜態(tài)方法都表示一個(gè)動(dòng)作。每個(gè)動(dòng)作負(fù)責(zé)完整的請(qǐng)求 / 響應(yīng)的流程,也就是說(shuō),所有前面提到的所有請(qǐng)求/響應(yīng)的過(guò)程都需要在每個(gè)動(dòng)作中來(lái)完成。
參數(shù)綁定
在控制層實(shí)現(xiàn)中很繁瑣但是必不可少的操作就是解析 HTTP 請(qǐng)求中的參數(shù)。不同的 Web 開(kāi)發(fā)框架會(huì)提供自己的參數(shù)解析方式。Play
框架也提供了相應(yīng)的支持。Play 框架可以解析 HTTP 請(qǐng)求中查詢字符串和 URI
路徑中包含的以及請(qǐng)求體中以格式編碼的參數(shù)。所有這些參數(shù)都放在 params
對(duì)象中,其中包含 get()
、getAll()
和 put()
等方法用來(lái)獲取和設(shè)置參數(shù)的值。除了這種傳統(tǒng)的使用方式之外,Play 框架還支持直接把參數(shù)的值綁定到動(dòng)作方法的參數(shù)上面。比如一個(gè)動(dòng)作方法的聲明是 show(String username)
,那么請(qǐng)求中的參數(shù) username
的值會(huì)在 show()
方法被調(diào)用時(shí)作為實(shí)際參數(shù)傳遞進(jìn)去。Play 框架會(huì)負(fù)責(zé)完成相應(yīng)的類(lèi)型轉(zhuǎn)換。值得一提的是對(duì)于日期類(lèi)型(java.util.Date
)的參數(shù),Play 框架支持多種類(lèi)型的日期格式的轉(zhuǎn)換。比如動(dòng)作方法的聲明是 display(Date postedAt)
,而請(qǐng)求的格式可能是 /display?postedAt=2010-09-22
,Play 框架會(huì)自動(dòng)完成相應(yīng)的類(lèi)型轉(zhuǎn)換。
除了常見(jiàn)的基本數(shù)據(jù)類(lèi)型之外,Play 框架還支持直接綁定領(lǐng)域?qū)ο蟮膶?shí)例。比如動(dòng)作方法的聲明是 create(Note note)
,可以在參數(shù)中直接指定對(duì)象實(shí)例的屬性的值。請(qǐng)求的格式可能是 /create?title=Note123&content=Good
。Play 框架會(huì)負(fù)責(zé)創(chuàng)建一個(gè) Note
類(lèi)的實(shí)例,并根據(jù)參數(shù)的值設(shè)置該實(shí)例的屬性 title
和 content
的值。這種綁定方式不僅支持簡(jiǎn)單對(duì)象,還支持嵌套對(duì)象和列表。比如 /create?tags[0]=ajax&tags[1]=web
可以設(shè)置列表類(lèi)型屬性 tags
的值。
Play 框架的這種綁定方式還支持文件對(duì)象,使得上傳文件變得非常簡(jiǎn)單。只需要在表單中添加文件上傳的控件(<input type="file">
)并使用 multipart/form-data
編碼來(lái)提交請(qǐng)求,在動(dòng)作方法的參數(shù)中就可以獲取到上傳文件對(duì)應(yīng)的 java.io.File
對(duì)象。比如動(dòng)作方法的聲明可能是 upload(File picture)
。上傳的文件被保存在臨時(shí)目錄中,在請(qǐng)求完成之后會(huì)被自動(dòng)刪除??梢栽趧?dòng)作方法中完成對(duì)上傳文件的操作。
返回響應(yīng)結(jié)果
在控制層的動(dòng)作方法完成了與業(yè)務(wù)邏輯相關(guān)的處理之后,需要把響應(yīng)返回給客戶端。響應(yīng)的結(jié)果可能是正確完成,也可能是出現(xiàn)錯(cuò)誤。Play 框架提供了方便的實(shí)現(xiàn)用來(lái)返回不同類(lèi)型的響應(yīng)。使用 play.mvc.Controller
類(lèi)提供的不同方法就可以生成這些響應(yīng)內(nèi)容。
- 請(qǐng)求正確完成,HTTP 狀態(tài)代碼為 200。使用
ok()
方法生成不帶內(nèi)容的響應(yīng)。使用 render()
方法來(lái)生成使用模板的響應(yīng)。使用 renderText()
方法生成 text/plain
類(lèi)型的純文本響應(yīng)。使用 renderXml()
方法生成 text/xml
類(lèi)型的 XML 格式的響應(yīng)。使用 renderJSON()
方法生成 application/json
類(lèi)型的 JSON 格式的響應(yīng)。使用 renderBinary()
方法生成二進(jìn)制內(nèi)容的響應(yīng)。
- 跳轉(zhuǎn)到新的頁(yè)面,HTTP 狀態(tài)代碼為 3XX。使用
redirect()
方法來(lái)跳轉(zhuǎn)到新的 URL。使用 notModified()
方法來(lái)返回狀態(tài)代碼 304。
- HTTP 狀態(tài)代碼 4XX。使用
unauthorized()
方法返回狀態(tài)代碼 401。使用 forbidden()
方法返回狀態(tài)代碼 403。使用 notFound()
方法返回狀態(tài)代碼 404。
- 服務(wù)器內(nèi)部錯(cuò)誤,HTTP 狀態(tài)代碼 5XX。使用
error()
方法返回狀態(tài)代碼 500。
從上面列出的方法可以看出,Play 框架使用一些有意義的方法名稱替換掉了難以記憶的 HTTP 狀態(tài)代碼,使用起來(lái)更加方便。同時(shí),對(duì)于常見(jiàn)的響應(yīng)格式,包括 HTML、XML、JSON 和二進(jìn)制內(nèi)容,都提供了相應(yīng)的方法,使得開(kāi)發(fā)人員不會(huì)遺漏掉響應(yīng)中 Content-Type
的聲明。
方法攔截
控制層的方法通常需要執(zhí)行一些橫切的邏輯,比如用戶認(rèn)證、加載通用信息和記錄日志等。在 Spring
框架中,這些橫切的邏輯是通過(guò)面向方面編程(AOP)的支持來(lái)實(shí)現(xiàn)的。Play
框架提供了更加簡(jiǎn)單易用的方法攔截支持,通過(guò)簡(jiǎn)單的標(biāo)注就可以定義一些執(zhí)行攔截操作的方法。這些方法必須非公開(kāi)的靜態(tài)方法。Play
框架支持的方法攔截標(biāo)注有 @Before
、@After
、@Finally
和 @With
等四種。
用 @Before
標(biāo)注的方法在動(dòng)作方法執(zhí)行之前被調(diào)用。@After
標(biāo)注的方法在動(dòng)作方法執(zhí)行之后被調(diào)用。@Finally
標(biāo)注的方法在動(dòng)作方法的響應(yīng)結(jié)果已經(jīng)成功生成之后被調(diào)用。這三個(gè)標(biāo)注都支持額外的兩個(gè)屬性:priority
表示標(biāo)注的方法的優(yōu)先級(jí),0 為最高;unless
是一個(gè)字符串?dāng)?shù)組,表示不適用此攔截方法的動(dòng)作方法的名稱。如 @Before(unless="index")
表示此攔截方法不會(huì)應(yīng)用在動(dòng)作方法 index()
上。
如果控制器類(lèi)中存在繼承體系結(jié)構(gòu)的話,父類(lèi)中聲明的攔截方法對(duì)于所有子類(lèi)的動(dòng)作方法都是適用的。在有些情況下,開(kāi)發(fā)人員可能希望把攔截方法定
義在不同的類(lèi)體系結(jié)構(gòu)中。由于 Java 不支持多繼承,無(wú)法通過(guò)繼承的方式來(lái)應(yīng)用來(lái)自不同類(lèi)體系結(jié)構(gòu)上的攔截方法。針對(duì)這種情況,Play 框架提供了
@With
標(biāo)注。在控制器類(lèi) ControllerA
中定義的攔截方法可以通過(guò) @With
標(biāo)注來(lái)應(yīng)用到另外一個(gè)控制器類(lèi) ControllerB
上,而且不通過(guò)繼承方式來(lái)實(shí)現(xiàn)。只需要在 ControllerB
中聲明 @With(ControllerA.class)
即可。
在介紹完 Play 框架的控制層之后,下面介紹視圖層。
回頁(yè)首
視圖層
Web 開(kāi)發(fā)框架的使用者都習(xí)慣于使用某種模板技術(shù)來(lái)生成 HTML 頁(yè)面,這些技術(shù)包括常見(jiàn)的 JSP、ASP 和 PHP 等。Play
框架也提供了自己的模板技術(shù),可以用來(lái)動(dòng)態(tài)的創(chuàng)建 HTML、XML、JSON 以及其它文本類(lèi)型的內(nèi)容。Play 框架的模板技術(shù)使用的是
Groovy 語(yǔ)言。Groovy 語(yǔ)言的靈活性和簡(jiǎn)潔性使得 Play
框架的模板簡(jiǎn)單而且易用。在模板中可以混用靜態(tài)內(nèi)容和生成動(dòng)態(tài)內(nèi)容的各種元素。在模板中可以使用的動(dòng)態(tài)元素如 表 3 所示。
表 3. 模板中可用的動(dòng)態(tài)元素
動(dòng)態(tài)元素 | 說(shuō)明 |
${...}
|
用來(lái)對(duì)一個(gè)表達(dá)式進(jìn)行求值。如 ${note.title} 的值是領(lǐng)域?qū)ο?note 的屬性 title 的值。 |
@{...} 和 @@{...}
|
用來(lái)生成調(diào)用控制器中動(dòng)作方法的 URL,可以用在頁(yè)面的鏈接中。@{...} 和 @@{...} 生成的分別是相對(duì) URL 和絕對(duì) URL。如 <a href="@{Application.index()}"> 首頁(yè) </> 生成一個(gè)指向首頁(yè)的鏈接。 |
&{...}
|
用來(lái)顯示經(jīng)過(guò)國(guó)際化之后的消息內(nèi)容。 |
*{...}*
|
用來(lái)添加注釋。如 *{ 這是注釋 }* 。 |
%{...}%
|
用來(lái)添加復(fù)雜的 Groovy 腳本,可以聲明變量和添加語(yǔ)句。 |
#{...}
|
用來(lái)調(diào)用 Play 框架的或是開(kāi)發(fā)人員自定義的標(biāo)簽。 |
Play 框架中的標(biāo)簽的作用相當(dāng)于 JSP 中的標(biāo)簽。Play 框架本身提供一些常用的標(biāo)簽,開(kāi)發(fā)人員也可以根據(jù)需要開(kāi)發(fā)自己的標(biāo)簽。Play 框架內(nèi)置提供的標(biāo)簽說(shuō)明如 表 4 所示。
表 4. Play 框架提供的標(biāo)簽
標(biāo)簽 | 說(shuō)明 |
a
|
用來(lái)生成指向控制器中動(dòng)作方法的 HTML 鏈接元素。如 #{a @Application.index()} 首頁(yè) #{/a} 。 |
if 、ifnot 、elseif 和 else
|
用來(lái)進(jìn)行條件判斷。 |
set 和 get
|
用來(lái)設(shè)置和獲取可以在模板中使用的變量。如 #{set email:'alex@example.org'} 設(shè)置了變量 email 的值,可以通過(guò) #{get 'email'} 來(lái)獲取。 |
script
|
用來(lái)生成一個(gè) <script> 元素引用 /public/javascripts 目錄下的 JavaScript 文件。如 #{script 'dojo.js'} 引用 /public/javascripts/dojo.js 文件。 |
stylesheet
|
作用與 script 類(lèi)似,不同的是使用 <link> 元素來(lái)引用 /public/stylesheets 目錄下的 CSS 文件。 |
list
|
用來(lái)遍歷一個(gè)對(duì)象集合。如 #{list items:notes, as:'note'} 用來(lái)遍歷對(duì)象集合 notes 。循環(huán)體中的每個(gè)對(duì)象用變量 note 來(lái)引用。在循環(huán)體的代碼中可以引用一些預(yù)定義的變量。這些變量有固定的名稱,但是會(huì)以循環(huán)體中的對(duì)象變量名作為前綴。在上面的例子中是 note_ 。如 note_index 表示當(dāng)前對(duì)象在集合中的序號(hào);note_isFirst 表示是否是集合中的第一個(gè)對(duì)象;note_isLast 表示是否是集合中的最后一個(gè)對(duì)象;note_parity 表示當(dāng)前對(duì)象在集合中序號(hào)的奇偶值,可能是 even 和 odd 。 |
i18n
|
用來(lái)使得支持國(guó)際化的消息文件可以用 JavaScript 來(lái)訪問(wèn)。在 JavaScript 代碼中可以 i18n() 方法來(lái)訪問(wèn)。如 i18n('app_title') 。 |
errors
|
用來(lái)遍歷驗(yàn)證錯(cuò)誤的集合。使用方式與 list 類(lèi)似,循環(huán)體中使用的對(duì)象變量名稱是 error 。 |
form
|
用來(lái)生成 HTML <form> 元素。 |
verbatim
|
禁用模板中的 HTML 轉(zhuǎn)義功能。 |
include
|
在當(dāng)前模板中引入另一個(gè)模板。如 #{include 'test.html'} 。 |
extends 和 doLayout
|
extends 使得當(dāng)前的模板繼承自另外一個(gè)模板。doLayout 用來(lái)在父模板中調(diào)用子模板。 |
在模板中可以使用來(lái)自不同地方的變量。首先是在模板生成的時(shí)候,由控制器中的動(dòng)作方法通過(guò) renderArgs
對(duì)象來(lái)添加的。如 renderArgs.put("username", "Alex")
就把一個(gè)變量 username
添加到了模板中。其次是一些隱含的變量,如 request
表示當(dāng)前的 HTTP 請(qǐng)求,session
表示當(dāng)前的會(huì)話,params
表示請(qǐng)求中的參數(shù)和 out
表示用來(lái)輸出響應(yīng)的 java.io.Writer
對(duì)象。最后就是可以通過(guò) #{set}
來(lái)設(shè)置變量。
模板的繼承
Play 框架中可以使用 #{extends}
和 #{doLayout}
來(lái)實(shí)現(xiàn)模板之間的繼承。模板的繼承機(jī)制對(duì)于實(shí)現(xiàn)靈活的頁(yè)面布局很有幫助。一個(gè)模板可以定義清楚頁(yè)面的基本布局結(jié)構(gòu),其它模板可以繼承此模板并添加具體的內(nèi)容。這樣就可以避免在不同模板中重復(fù)相同的頁(yè)面元素。
在父模板中可以包含任意的內(nèi)容。在需要由子模板填充的位置,使用 #{doLayout /}
進(jìn)行聲明即可。在子模板中通過(guò) #{extends}
來(lái)聲明所繼承的模板。如 #{extends 'main.html'}
就聲明繼承自模板 main.html
。當(dāng)子模板被生成之后,將包含父模板中的內(nèi)容。而子模板中只需要定義擴(kuò)展的內(nèi)容即可。
自定義標(biāo)簽
Play 框架自身提供的標(biāo)簽只能解決一些常見(jiàn)的需求,很多時(shí)候開(kāi)發(fā)人員需要根據(jù)需要開(kāi)發(fā)出自己的標(biāo)簽。一個(gè)標(biāo)簽的定義非常簡(jiǎn)單,就是一個(gè)模板文件。模板文件被存放在 app/views/tags
目錄下,文件的名稱就是標(biāo)簽的名稱。在標(biāo)簽對(duì)應(yīng)的模板里面,開(kāi)發(fā)人員可以添加任意的內(nèi)容。標(biāo)簽也是支持傳入?yún)?shù)的。在標(biāo)簽對(duì)應(yīng)的模板文件中可以用在參數(shù)名稱前面加上 _
的方式來(lái)引用參數(shù)的值。比如一個(gè)標(biāo)簽在使用時(shí)的方式是 #{myTag name:'Alex' /}
,那么在該標(biāo)簽的模板文件中,就可以用 ${_name}
來(lái)引用參數(shù) name
的值。有些標(biāo)簽是支持在使用的時(shí)候添加標(biāo)簽體的,如 #{anotherTag} 測(cè)試文字 #{/anotherTag}
。對(duì)于這種情況,在標(biāo)簽的模板文件中可以用 #{doBody}
來(lái)引用標(biāo)簽體中的內(nèi)容。
在介紹完 Play 框架的視圖層之后,下面介紹 HTTP 路由。
回頁(yè)首
HTTP 路由
在前面介紹過(guò),Play 框架中的控制器用來(lái)接受 HTTP 請(qǐng)求并返回相應(yīng)的響應(yīng)。這個(gè)過(guò)程的重要一環(huán)就是 HTTP 請(qǐng)求的 URI 與控制器之間的映射關(guān)系。Play 框架提供了靈活的 HTTP 路由功能來(lái)完成這個(gè)映射。路由信息被保存在 config/routes
文件中,采用簡(jiǎn)單的方式進(jìn)行聲明。每條路由記錄包含 3 個(gè)元素,分別是 HTTP 方法的名稱、匹配的 URI 模式以及對(duì)應(yīng)的控制器動(dòng)作方法。路由記錄表示的含義是當(dāng)使用給定的 HTTP 方法來(lái)請(qǐng)求對(duì)應(yīng)模式的 URI 的時(shí)候,控制器動(dòng)作方法就會(huì)被調(diào)用。
Play 框架支持的 HTTP 方法有 GET
、POST
、PUT
、DELETE
和 HEAD
。使用通配符 *
可以匹配任何方法。在 URI 模式的聲明中可以使用正則表達(dá)式來(lái)表示復(fù)雜的映射規(guī)則。URI 模式中還可以使用 {...}
來(lái)聲明動(dòng)態(tài)的部分。每個(gè)動(dòng)態(tài)部分都是有名稱的,可以在控制器動(dòng)作方法中通過(guò) params
對(duì)象來(lái)獲取。比如,/notes/home
這樣的 URI 模式會(huì)匹配 /notes/home
,但是 /notes/{id}
可以匹配 /notes/123
和 /notes/abc
,而且 URI 模式中 /notes/
后面的部分可以作為參數(shù) id
的值被獲取到。URI 模式 /notes/{<[0-9]+>id}
使用了正則表達(dá)式,只會(huì)匹配 /notes/
后面緊跟的全是數(shù)字的情況。在聲明控制器的動(dòng)作方法的時(shí)候,需要使用帶名稱空間的全名,如 myapp.Notes.show
。有些動(dòng)作方法是帶參數(shù)的,可以在聲明的時(shí)候預(yù)先綁定一些參數(shù)值,這樣可以方便的添加一些 URI 別名。比如動(dòng)作方法 Notes.show()
有一個(gè)參數(shù) id
用來(lái)指明要顯示的內(nèi)容的 ID。如果參數(shù) id
的值為 0,則會(huì)顯示所有內(nèi)容的一個(gè)列表。這樣的話,就可以定義一個(gè)類(lèi)似 GET /notes/all Notes.show(id:0)
的路由聲明。這樣暴露出來(lái)的 URI 更加簡(jiǎn)潔和易于記憶。
在路由文件中的路由聲明是按照從上到下的優(yōu)先級(jí)來(lái)進(jìn)行匹配的。比較具體的 URI 模式應(yīng)該放在比較通用的模式之前。對(duì)于靜態(tài)文件,可以通過(guò)一個(gè)特殊的動(dòng)作方法 staticDir
進(jìn)行聲明。比如 GET /files staticDir:files
就聲明了 files
目錄中包含的是靜態(tài)文件。
在介紹完 HTTP 路由之后,下面介紹 Play 框架獨(dú)特的無(wú)狀態(tài)的體系結(jié)構(gòu)。
回頁(yè)首
無(wú)狀態(tài)的體系結(jié)構(gòu)
HTTP 協(xié)議本身就被設(shè)計(jì)成無(wú)狀態(tài)的,采用請(qǐng)求 / 響應(yīng)的模式。不同的請(qǐng)求之間并不存在相互關(guān)系。但是這種架構(gòu)模式在開(kāi)發(fā)某些 Web
應(yīng)用的時(shí)候不是很方便。有些應(yīng)用要求用戶進(jìn)行認(rèn)證登錄之后才能進(jìn)行某些操作。同樣的
URL,認(rèn)證和未認(rèn)證用戶看到的內(nèi)容是不同的。而且用戶認(rèn)證成功之后,他應(yīng)該在一段時(shí)間內(nèi)保持這種認(rèn)證狀態(tài)。否則的話,用戶每次都需要輸入用戶名和密碼才
能訪問(wèn)受限的內(nèi)容。對(duì)于這種情況,很多 Web 開(kāi)發(fā)框架提供了會(huì)話的支持,允許應(yīng)用保存一些與會(huì)話相關(guān)的數(shù)據(jù)。Java Servlet 規(guī)范中的 javax.servlet.http. HttpSession
就是一種會(huì)話的接口。應(yīng)用的服務(wù)器會(huì)負(fù)責(zé)維護(hù)每個(gè)會(huì)話相關(guān)的數(shù)據(jù)。這些數(shù)據(jù)可以通過(guò)一個(gè)會(huì)話 ID 來(lái)進(jìn)行標(biāo)識(shí)。這個(gè)標(biāo)識(shí)會(huì)利用瀏覽器的 cookie
機(jī)制保存在瀏覽器端,也可以作為請(qǐng)求 URL 的參數(shù)來(lái)傳遞。服務(wù)器端通過(guò)此標(biāo)識(shí)來(lái)識(shí)別每個(gè)會(huì)話。在處理相應(yīng)的請(qǐng)求的時(shí)候,就可以根據(jù)會(huì)話 ID
來(lái)獲取保存在服務(wù)器端上的會(huì)話數(shù)據(jù)。會(huì)話機(jī)制的問(wèn)題是會(huì)影響應(yīng)用的可伸縮性。如果一個(gè)應(yīng)用使用多臺(tái)服務(wù)器的話,就需要額外的機(jī)制來(lái)保證同一用戶在不同機(jī)器
上面的會(huì)話是同步的。而無(wú)狀態(tài)的實(shí)現(xiàn)則不存在這個(gè)問(wèn)題,對(duì)于某一個(gè)請(qǐng)求,由不同機(jī)器來(lái)處理的結(jié)果都是相同的。
Play 框架的設(shè)計(jì)架構(gòu)就是無(wú)狀態(tài)的。它沒(méi)有提供服務(wù)器端的機(jī)制用來(lái)維護(hù)跨多個(gè)請(qǐng)求的數(shù)據(jù)。如果確實(shí)需要保存這樣的數(shù)據(jù)的話,可以考慮下面幾種方案:
- 保存在 Session 或 Flash 作用域中。Play
框架中仍然有會(huì)話的機(jī)制,但是并沒(méi)有提供在服務(wù)器端保存會(huì)話數(shù)據(jù)的能力。會(huì)話數(shù)據(jù)是保存在瀏覽器的 cookie
中的,由瀏覽器在每次請(qǐng)求的時(shí)候自動(dòng)發(fā)送。通過(guò)這種方式來(lái)達(dá)到維護(hù)會(huì)話數(shù)據(jù)的目的。由于會(huì)話數(shù)據(jù)是保存在 cookie
中,其大小是有限制的,一般不能超過(guò) 4K 字節(jié),而且只能保存字符串類(lèi)型的數(shù)據(jù)。Flash 作用域和會(huì)話一樣,也是通過(guò) cookie
來(lái)保存的。所不同的是,F(xiàn)lash 作用域中的數(shù)據(jù)只在下次請(qǐng)求中是有效的。
- 保存在持久化的數(shù)據(jù)存儲(chǔ)中,如數(shù)據(jù)庫(kù)中。如果需要在多個(gè)請(qǐng)求中使用同一個(gè)領(lǐng)域?qū)ο蟮脑?,可以把這個(gè)對(duì)象的 ID 保存在 Session 或 Flash 作用域中,而在控制器動(dòng)作方法中使用此 ID 來(lái)從數(shù)據(jù)庫(kù)中查詢相應(yīng)的對(duì)象。
- 保存在暫時(shí)性數(shù)據(jù)存儲(chǔ)中,如緩存中。Play 框架內(nèi)置了緩存的支持,通過(guò)調(diào)用類(lèi)
play.cache.Cache
就可以對(duì)緩存進(jìn)行操作。與使用持久化存儲(chǔ)類(lèi)似,緩存中的鍵的值可以保存在 Session 或 Flash 作用域中。
對(duì)于熟悉了 Java Servlet 規(guī)范的開(kāi)發(fā)人員來(lái)說(shuō),需要一些時(shí)間來(lái)適應(yīng) Play 框架的這種無(wú)狀態(tài)的體系結(jié)構(gòu)。不過(guò)這種結(jié)構(gòu)對(duì)于應(yīng)用的可伸縮性來(lái)說(shuō),確實(shí)是非常有好處的。
介紹完無(wú)狀態(tài)的體系結(jié)構(gòu)之后,下面介紹一些其它話題。
回頁(yè)首
其它話題
測(cè)試
Play 框架對(duì)應(yīng)用的測(cè)試也提供了良好的支持。Play 框架一共支持三種類(lèi)型的測(cè)試,分別是單元測(cè)試、功能測(cè)試和界面測(cè)試。單元測(cè)試主要用來(lái)測(cè)試應(yīng)用的模型層代碼。單元測(cè)試用例的 Java 類(lèi)繼承自 play.test.UnitTest
,可以使用 JUnit 4 提供的標(biāo)注和斷言。功能測(cè)試主要用來(lái)測(cè)試應(yīng)用的控制層代碼。功能測(cè)試用例的 Java 類(lèi)繼承自 play.test.FunctionalTest
。在測(cè)試用例中,可以通過(guò) GET()
、POST()
、PUT()
、DELETE()
和 makeRequest()
等方法來(lái)發(fā)出 HTTP 請(qǐng)求,也可以直接調(diào)用控制器中的動(dòng)作方法。除此之外,還可以使用一些與 HTTP 響應(yīng)相關(guān)的斷言。如 assertStatus()
、assertContentType()
和 assertHeaderEquals()
分別用來(lái)驗(yàn)證 HTTP 狀態(tài)代碼、內(nèi)容類(lèi)型和 HTTP 頭。界面測(cè)試使用 Selenium 工具來(lái)進(jìn)行。開(kāi)發(fā)人員可以使用 Selenium 的語(yǔ)法來(lái)編寫(xiě)測(cè)試用例,也可以使用 Play 框架提供的 #{selenium}
標(biāo)簽。
在進(jìn)行測(cè)試的時(shí)候,需要準(zhǔn)備一些測(cè)試數(shù)據(jù)。測(cè)試數(shù)據(jù)可以用 YAML 的格式保存在文本文件中,并通過(guò) play.test.Fixtures.laod()
方法來(lái)加載這些數(shù)據(jù)到數(shù)據(jù)庫(kù)中。當(dāng)測(cè)試結(jié)束之后,可以通過(guò) deleteAll()
方法來(lái)刪除這些數(shù)據(jù)。
在測(cè)試的時(shí)候,需要用 play test
命令以測(cè)試模式啟動(dòng)應(yīng)用,再用瀏覽器訪問(wèn) http://localhost:9000/@tests
進(jìn)行測(cè)試。
任務(wù)調(diào)度
在 Web 應(yīng)用開(kāi)發(fā)中,有時(shí)候會(huì)需要定期執(zhí)行一些調(diào)度任務(wù),比如數(shù)據(jù)庫(kù)備份和數(shù)據(jù)同步等。這些任務(wù)不是通過(guò) HTTP 請(qǐng)求來(lái)觸發(fā)的,而是定時(shí)執(zhí)行的。Play 框架提供了內(nèi)置的任務(wù)調(diào)度支持的能力。創(chuàng)建新任務(wù)的時(shí)候,只需要繼承自 play.jobs.Job
類(lèi),并覆寫(xiě) doJob()
方法即可。如果要?jiǎng)?chuàng)建的任務(wù)有返回結(jié)果的話,覆寫(xiě) doJobWithResult()
方法即可。任務(wù)創(chuàng)建完成之后,可以選擇不同的調(diào)度方式。一種方式是在應(yīng)用啟動(dòng)的時(shí)候執(zhí)行一次。只需要在任務(wù)的 Java 類(lèi)上添加標(biāo)注 @OnApplicationStart
即可。對(duì)于定期執(zhí)行的任務(wù),Play 框架提供了兩個(gè)標(biāo)注:一個(gè)是 @Every
,用來(lái)按照固定的時(shí)間間隔調(diào)度任務(wù),如 @Every("1h")
聲明任務(wù)每個(gè)小時(shí)執(zhí)行一次;另外一個(gè)是 @On
,用來(lái)聲明描述調(diào)度策略的 CRON 表達(dá)式。
安全
Play 框架提供了對(duì) Web 應(yīng)用安全性方面的支持,可以防范一些常見(jiàn)的攻擊方式。前面提到過(guò),Play
框架中的會(huì)話數(shù)據(jù)是保存在瀏覽器的 cookie
中的。這些數(shù)據(jù)是經(jīng)過(guò)簽名的,可以防止被惡意攻擊者所篡改。應(yīng)用中的重要數(shù)據(jù)也不應(yīng)該保存在會(huì)話中。Play 框架中的模板在輸出 HTML
內(nèi)容的時(shí)候,會(huì)自動(dòng)對(duì)內(nèi)容進(jìn)行轉(zhuǎn)義,可以防范跨站點(diǎn)腳本攻擊。對(duì)于 SQL 注入攻擊,開(kāi)發(fā)人員應(yīng)該盡量使用提供的 find()
方法來(lái)查詢領(lǐng)域?qū)ο?。?duì)于自己創(chuàng)建的查詢語(yǔ)句,應(yīng)該在語(yǔ)句中使用占位符并進(jìn)行參數(shù)綁定,而不是通過(guò)字符串相加的方式來(lái)創(chuàng)建。為了防范跨站點(diǎn)請(qǐng)求偽造,Play 框架中的控制器的動(dòng)作方法都可以使用 checkAuthenticity()
方法來(lái)聲明調(diào)用此方法時(shí)的請(qǐng)求中必須包含合法的令牌。這個(gè)令牌用來(lái)確保當(dāng)前請(qǐng)求是由應(yīng)用自身發(fā)出的,而不是被偽造的。通過(guò) session.getAuthenticityToken()
方法可以生成一個(gè)只對(duì)當(dāng)前會(huì)話有效的令牌,需要在請(qǐng)求的時(shí)候附帶此令牌。如果是通過(guò)頁(yè)面上的表單來(lái)提交請(qǐng)求的話,Play 框架也提供了一個(gè)標(biāo)簽 #{authenticityToken /}
用來(lái)生成一個(gè)包含了令牌的隱藏域,可以直接在模板中使用。
回頁(yè)首
總結(jié)
Play 框架作為一個(gè)優(yōu)秀的 Java Web 應(yīng)用開(kāi)發(fā)框架,可以幫助開(kāi)發(fā)人員快速高效的構(gòu)建 Web
應(yīng)用。它為開(kāi)發(fā)人員提供了一個(gè)良好的基礎(chǔ)架構(gòu),并屏蔽了很多底層的實(shí)現(xiàn)細(xì)節(jié)。開(kāi)發(fā)人員可以用一個(gè)簡(jiǎn)單的視角來(lái)看待 Web
應(yīng)用開(kāi)發(fā),而不需要關(guān)心過(guò)多的細(xì)節(jié)。Web 開(kāi)發(fā)人員可以熟悉 Play 框架,并在開(kāi)發(fā)中選用這個(gè)框架。