play框架05--控制層
5.1、概述
Play的控制層位於應用的controllers包中,其中的Java類即為控制器(Controller)。如圖4.1所示,Application.java和MyController.java都屬於控制層。
(圖4.1 控制器為controllers包中的Java類)
控制器需要繼承play.mvc.Controller:
package controllers;
import models.Client;
import play.mvc.Controller;
public class Clients extends Controller {
public static void show(Long id) {
Client client = Client.findById(id);
render(client);
}
public static void delete(Long id) {
Client client = Client.findById(id);
client.delete();
}
}
在控制器中,每個以public static宣告,返回值為void的方法稱為Action。Action的方法宣告如下:
public static void action_name(params…);
Play會自動將HTTP請求引數轉化為與之相匹配的Action方法引數,這部分內容會在後面的獲取HTTP引數小節進行詳細講解。通常情況下,Action方法無需返回任何值,以呼叫結果方法來終止執行。在上述例子中,render(…)方法就是用來渲染模板的結果方法。
HTTP請求中往往包含各種引數,這些引數的傳遞形式如下:
- URI路徑:在路徑/clients/1541中,1541是URI的動態部分。
- 查詢字串:clients?id=1541。
- 請求體:如果請求是來自HTML的表單提交(GET或者POST),那麼請求體包含的是表單資料(採用x-www-urlform-encoded作為編碼格式)。
針對以上幾種情況,Play會自動提取這些HTTP引數並將他們儲存在Map<String,String>型別的變數中,以引數名作為Map的key。這些引數名分別來自於:
- URI中動態部分的名稱(在routes檔案中定義)。
- 查詢字串中“名稱/值”對中的名稱部分 。
- 採用x-www-urlform-encoded編碼的表單資料的引數名。
5.2獲取HTTP引數
5.2.1 使用Map引數#
HTTP請求中引數物件(params)在任何控制器中都是可訪問的(該實現在play.mvc.Controller超類中定義),它包含了當前所有HTTP請求的引數,並且可以通過get()方法得到,具體如下:
public static void show(){
String id=params.get("id");
String[] names=params.getAll("name");
}
這些引數也可以進行型別轉換:
public static void show(){
Long id=params.get("id",Long.class);
}
本節將推薦一種更好的解決方案。Play框架提供了自動將Action宣告的引數與HTTP引數自動匹配的功能(只需要保持Action方法的引數名和HTTP引數名一致即可):
/clients?id=1541
Action方法可以在宣告中以id作為引數,以此匹配HTTP中變數名為id的引數:
public static void show(String id){
System.out.println(id);
}
當然,也可以使用其他Java引數型別,而不僅僅是String。在下面的例子中框架會自動將引數轉換為正確的資料型別:
public static void show(Long id){
System.out.println(id);
}
如果引數含有多個值,那麼可以定義陣列引數,具體如下:
public static void show(Long[] id){
for(Long anId:id){
System.out.println(anId);
}
}
引數甚至可以是List型別:
public static void show(List<Long> id){
for(Long anId:id){
System.out.println(anId);
}
}
如果Action與HTTP之間的引數無法匹配,Play會將該引數設定為預設值(通常情況下物件型別為null,原始資料型別為0)。如果引數可以匹配但不能正確進行資料轉換,那麼Play會先生成錯誤並新增到驗證器的error物件集合中,然後將引數設定為預設值。
4.2.2 高階HTTP繫結#
簡單型別
Play可以實現所有Java原生的簡單資料型別的自動轉換,主要包括:int,long,boolean,char,byte,float,double,Integer,Long,Boolean,Char,String,Float,Double。
日期型別
如果HTTP引數字串符合以下幾種資料格式,框架能夠自動將其轉換為日期型別:
- yyyy-MM-dd'T'hh:mm:ss’Z' // ISO8601 + timezone
- yyyy-MM-dd'T'hh:mm:ss" // ISO8601
- yyyy-MM-dd
- yyyyMMdd'T'hhmmss
- yyyyMMddhhmmss
- dd'/'MM'/'yyyy
- dd-MM-yyyy
- ddMMyyyy
- MMddyy
- MM-dd-yy
- MM'/'dd'/'yy
而且還能通過@As註解,指定特定格式的日期,例如:
archives?from=21/12/1980
public static void articlesSince(@As("dd/MM/yyyy") Date from) {
List<Article> articles = Article.findBy("date >= ?", from);
render(articles);
}
也可以根據不同地區的語言習慣對日期的格式做進一步的優化,具體如下:
public static void articlesSince(@As(lang={"fr,de","*"},
value={"dd-MM-yyyy","MM-dd-yyyy"}) Date from) {
List<Article> articles = Article.findBy("date >= ?", from);
render(articles);
}
在這個例子中,對於法語和德語的日期格式是dd-MM-yyyy,其他語言的日期格式是MM-dd-yyyy。語言值可以通過逗號隔開,且需要與引數的個數相匹配。
如果沒有使用@As註解來指定,Play會採用框架預設的日期格式。為了使預設的日期格式能夠正常工作,按照以下方式編輯application.conf檔案:
date.format=yyyy-MM-dd
在application.conf檔案中設定預設的日期格式之後,就可以通過${date.format()}方法對模板中的日期進行格式化操作了。
日曆型別
日曆型別和日期型別非常相像,當然Play會根據本地化選擇預設的日曆型別。讀者也可以通過@Bind註解來使用自定義的日曆型別。
檔案型別
在Play中處理檔案上傳是件非常容易的事情,首先通過multipart/form-data編碼的請求將檔案傳送到伺服器,然後使用java.io.File型別提取檔案物件:
public static void create(String comment, File attachment) {
String s3Key = S3.post(attachment);
Document doc = new Document(comment, s3Key);
doc.save();
show(doc.id);
}
新建立檔案的名稱與原始檔案一致,儲存在應用的臨時檔案下(Application_name/tmp)。在實際開發中,需要將其拷貝到安全的目錄,否則在請求結束後會丟失。
陣列和集合型別
所有Java支援的資料型別都可以通過陣列或者集合的形式來獲取。陣列形式:
public static void show(Long[] id){List形式:
...
}
public staic void show(List<Long> id){集合形式:
...
}
public static void show(Set<Long> id){Play還可以處理Map<String, String>對映形式:
...
}
public static void show(Map<String, String> client) {例如下面的查詢字串會轉化為帶有兩個元素的map型別,第一個元素key值為name,value為John;第二個元素key值為phone,value為111-1111, 222-2222。:
...
}
?user.name=John&user.phone=111-1111&user.phone=222-2222
POJO物件繫結
Play使用同名約束規則(即HTTP引數名必須與模型類中的屬性名一致),自動繫結模型類:
public static void create(Client client){
client.save();
show(client);
}
以下的查詢字串可以通過上例的Action建立client:
?client.name=Zenexity&client.email=[email protected].fr框架通過Action建立Client的例項,並將HTTP引數解析為該例項的屬性。如果出現引數無法解析或者型別不匹配的情況,會自動忽略。
引數繫結是遞迴執行的,這意味著可以深入到關聯物件:
?client.name=Zenexity
&client.address.street=64+rue+taitbout
&client.address.zip=75009
&client.address.country=France
Play的引數繫結提供陣列的支援,可以將物件id作為對映規則,更新一組模型物件。假設Client模型有一組宣告為List<Customer>的customers屬性,那麼更新該屬性需要使用如下查詢字串:
?client.customers[0].id=123
&client.customers[1].id=456
&client.customers[2].id=789
4.2.3 JPA物件繫結#
通過HTTP引數還可以實現JPA物件的自動繫結。Play會識別HTTP請求中提供的引數user.id,自動與資料庫中User例項的id進行匹配。一旦匹配成功,HTTP請求中的其他User屬性引數可以直接更新到資料庫相應的User記錄中:
public static void save(User user){
user.save();
}
和POJO對映類似,可以使用JPA繫結來更改物件,但需要注意的是必須為每個需要更改的物件提供id:
user.id = 1
&user.<