play框架使用起來(7)

1 高級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=contact@zenexity.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

2 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.name=morten
&user.address.id=34
&user.address.street=MyStreet

3 自定義綁定#

      綁定機制支持自定義功能,可以按照讀者的需求,自定義參數綁定的規則。


@play.data.binding.As

      @play.data.binding.As註解可以依據配置提供綁定的支持。下例使用DateBinder指定日期的數據格式:

public static void update(@As("dd/MM/yyyy") Date updatedAt) {
   
...
}

      @As註解還具有國際化支持,可以爲每個本地化提供專門的註解:

public static void update(
       
@As(
            lang
={"fr,de","en","*"},
            value
={"dd/MM/yyyy","dd-MM-yyyy","MM-dd-yy"}
       
)
       
Date updatedAt
   
) {
   
...
}

      @As註解可以和所有支持它的綁定一起工作,包括用戶自定義的綁定。以下是使用ListBinder的例子:

public static void update(@As(",") List<String> items) {
   
...
}

      上例中的綁定使用逗號將字符串分隔成List。

      

      @play.data.binding.NoBinding

      @play.data.binding.NoBinding註解允許對不需要綁定的屬性進行標記,以此來解決潛在的安全問題。比如:

//User爲Model類
public class User extends Model {
   
@NoBinding("profile") public boolean isAdmin;
   
@As("dd, MM yyyy") Date birthDate;
   
public String name;
}

//editProfile爲Action方法
public static void editProfile(@As("profile") User user) {
   
...
}

      在上述例子中,user對象的isAdmin屬性始終不會被editProfile方法(Action)所修改,即使有惡意用戶僞造POST表單提交user.isAdmin=true信息,也不能修改user的isAdmin權限。


play.data.binding.TypeBinder

      @As註解還提供完全自定義綁定的功能。自定義綁定必須是TypeBinder類的實現:

public class MyCustomStringBinder implements TypeBinder<String> {
 
   
public Object bind(String name, Annotation[] anns, String value,
   
Class clazz) {
       
return "!!" + value + "!!";
   
}
}
      定義完成後,就可以在任何Action中使用它:
public static void anyAction(@As(binder=MyCustomStringBinder.class) String name) {
       
...
}

@play.data.binding.Global

      Play中還可以自定義全局Global綁定。以下是爲java.awt.Point類定義綁定的例子:

@Global
public class PointBinder implements TypeBinder<Point> {
 
   
public Object bind(String name, Annotation[] anns, String value,
   
Class class) {
       
String[] values = value.split(",");
       
return new Point(
           
Integer.parseInt(values[0]),
           
Integer.parseInt(values[1])
       
);
   
}
}

      因此外部模塊很容易通過自定義綁定來提供可重用的類型轉換組件。




3、結果返回

 Action方法需要對客戶端作出HTTP響應,最簡單的方法就是發送結果對象。當對象發送後,常規的執行流程就會中斷。以下面這段代碼爲例,最後一句System.out.println的輸出不會被執行:

public static void show(Long id) {
   
Client client = Client.findById(id);
    render
(client);
   
System.out.println("This message will never be displayed !");
}

      render(…)方法向模板發送client對象,之後的其他語句將不會執行,所以在控制檯中,並不會打印出“This message will never be displayed !”。

3.1 返回文本內容#

      renderText(…)方法直接將文本內容寫到底層HTTP響應中:

public static void countUnreadMessages(){
   
Integer unreadMessages=MessagesBos.countUnreadMessage();
    renderText
(unreadMessages);
}

      也可以通過Java標準的格式化語法對輸出的文本進行處理:

public static void countUnreadMessages(){
   
Integer unreadMessages=MessagesBox.countUnreadMessages();
    renderText
("There are %s unread messages",unreadMessages);
}

3.2 返回JSON字符串#

      越來越多的應用使用JSON作爲數據格式進行交互,Play對此進行了很好的封裝,只需要使用renderJSON(…)方法就可以輕鬆地返回JSON字符串。在使用renderJSON(…)方法時,Play會自動將服務器返回的響應的content type值設置爲application/json,並且將renderJSON(...)方法中的參數以JSON格式返回。

      在使用renderJSON(...)方法時,可以輸入字符串格式的參數,自行指定JSON返回的內容。

public static void countUnreadMessages() {
   
Integer unreadMessages = MessagesBox.countUnreadMessages();
    renderJSON
("{\"messages\": " + unreadMessages +"}");
}

      以上範例在使用renderJSON(...)方法時,傳入了拼接成JSON格式的字符串參數。Play框架會對其進行自動設置,改變content type的值爲application/json。

      當然,renderJSON(...)方法的功能並不只有這些。因爲大部分的應用需求,都會要求服務端返回比較複雜的JSON格式,如果都採用字符串拼接的方式組成JSON內容,就太不人性化了。renderJSON(...)的輸入參數還可以是複雜的對象,如果採用這種方式使用renderJSON(...)方法,Play在執行renderJSON(...)時,底層會先調用GsonBuilder將對象參數進行序列化,之後再將複雜的對象以JSON的格式返回給請求。這樣開發者就可以完全透明地使用renderJSON(...)方法,不需要做其他的任何操作了,以下代碼範例將會展示renderJSON(...)的這個功能。

public static void getUnreadMessages() {
   
List<Message> unreadMessages = MessagesBox.unreadMessages();
    renderJSON
(unreadMessages);
}

3.3 返回XML字符串#

      與使用renderJSON(...)方法返回JSON內容類似,如果用戶希望以XML格式對內容進行渲染,可以在Controller控制器中直接使用renderXml(…)方法。 使用renderXml(...)方法時,Play會自動將服務器返回的響應的content type值設置爲application/xml。

      在使用renderXml(...)方法時,可以輸入字符串格式的參數,自行指定XML返回的內容。

public static void countUnreadMessages() {
   
Integer unreadMessages = MessagesBox.countUnreadMessages();
    renderXml
("<unreadmessages>"+unreadMessages+"</unreadmessages>");
}

      如果希望將複雜的對象以XML格式進行渲染,可以在使用renderXml(...)方法時輸入org.w3c.dom.Document格式的對象,或者直接輸入POJO對象。以POJO對象作爲參數使用renderXml(...)方法時,Play會使用XStream將其進行序列化操作。同樣的,這些序列化操作都不需要由開發者去做,全部交給Play就行,開發者需要做的就是按照規範簡單地調用renderXml(...)方法即可。

public static void getUnreadMessages() {
   
Document unreadMessages = MessagesBox.unreadMessagesXML();
    renderXml
(unreadMessages);

3.4 返回二進制內容#

      Play爲開發者提供了renderBinary(...)方法,可以非常方便的返回二進制數據(如存儲在服務器裏的文件、圖片等)給客戶端。以下代碼範例將會展示如何使用renderBinary(...)方法進行二進制圖片的渲染。

public static void userPhoto(long id) { 
   
final User user = User.findById(id);
   response
.setContentTypeIfNotSet(user.photo.type());
   java
.io.InputStream binaryData = user.photo.get();
   renderBinary
(binaryData);
}

      首先,開發者需要建立用於持久化的域模型User,該User模型具有play.db.jpa.Blob類型的屬性photo。play.db.jpa.Blob是經過Play封裝的特有的屬性類型,可以很方便的處理二進制數據。之後,在Controller控制器中使用時,需要調用域模型的findById(...)方法加載持久化的數據,並將圖片以二進制數據流InputStream的形式進行渲染。


3.5 下載附件功能#

    如果開發者希望將存儲在服務器端的文件,採用下載的形式渲染給客戶端用戶,需要對HTTP的header進行設置。通常的做法是通知Web瀏覽器將二進制響應數據以附件的形式,下載至用戶的本地電腦上。在Play中完成這個功能非常簡單,只需要在使用renderBinary(...)方法時多傳入一個文件名的參數即可。這樣做會觸發renderBinary(...)的額外功能,提供文件名並設置響應頭的Content-Disposition屬性。之後二進制文件(包括圖片)將會以附件下載的形式,渲染給用戶。

public static void userPhoto(long id) { 
   
final User user = User.findById(id);
   response
.setContentTypeIfNotSet(user.photo.type());
   java
.io.InputStream binaryData = user.photo.get();
   renderBinary
(binaryData, user.photoFileName);
}

3.6 執行模板#

      如果需要響應的內容比較複雜,那麼就應該使用模板來進行處理:

public class Clients extends Controller{
   
public static void index(){
        render
();
   
}
}

      模板的名稱遵從Play的約束規則,默認的模板路徑採用控制器和Action的名稱相結合的方式來定義,比如在上述例子中,模板對應的路徑爲:app/views/Clients/index.html。


3.7 爲模板作用域添加數據#

      通常情況下模板文件都需要數據進行顯示,可以使用renderArg()方法爲模板注入數據:

public class Clients extends Controller {
 
   
public static void show(Long id) {
       
Client client = Client.findById(id);
        renderArgs
.put("client", client);
        render
();    
   
}
 
}

      在模板執行過程當中,client變量可以被使用:

<h1>Client ${client.name}</h1>

3.8 更簡單方式#

      這裏介紹一種更簡單的方式向模板傳遞數據。直接使用render(…)方法注入模板數據:

public static void show(Long id){
   
Client client=Client.findById(id);
    render
(client);
}

      以該方式進行數據傳遞,模板中可訪問的變量與Java本地變量的名稱(也就是render()方法中的參數名)一致。當然也可以同時傳遞多個參數:

public static void show(Long id){
   
Client client=Client.findById(id);
    render
(id,client);
}


注意:

render()方法只允許傳遞本地變量。



3.9 指定其他模板進行渲染#

      如果讀者不希望使用默認的模板進行渲染,那麼可以在renderTemplate(…)方法的第一個參數中指定其他自定義的模板路徑,例如:

public static void show(Long id) {
   
Client client = Client.findById(id);
    renderTemplate
("Clients/showClient.html", id, client);    

3.10 重定向URL#

      redirect(…)方法產生HTTP重定向響應,可以將請求轉發到其他URL:

public static void index(){
    redirect
("http://www.oopsplay.org");
}

3.11 自定義Web編碼#

      Play推薦開發者使用UTF-8作爲應用開發的編碼格式,如果不進行任何設置,Play框架默認使用的也就是UTF-8格式。但是具體情況並不總是這麼理想,有些特殊的需求可能要求某些響應(response)的格式爲ISO-8859-1,或者要求整個應用都必須保持ISO-8859-1編碼。

爲當前響應設置編碼格式

      如果需要改變某一個響應(response)的編碼格式,可以直接在Controller控制器中進行修改,具體做法如下所示:

response.encoding = "ISO-8859-1";
      當開發表單提交功能時,如果開發者希望某一表單提交的內容採用非框架默認使用的編碼(即Play框架採用默認的編碼格式UTF-8,而該form表單提交的內容希望採用ISO-8859-1編碼格式),Play的做法有一些特殊。在書寫form表單的HTML代碼時,需要對採用何種編碼格式進行兩次標識。首先需要在<form>標籤中添加accept-charset屬性(如:accept-charset="ISO-8859-1"),accept-charset屬性會通知瀏覽器當form表單提交的時候,採用何種編碼格式;其次,需要在form表單中添加hidden隱藏域,name屬性規定爲“_charset_”,value屬性爲具體需要的編碼格式,這樣做的目的是當form提交的時候,可以通知服務端的Play採用何種編碼方式,具體範例如下:
<form action="@{application.index}" method="POST" accept-charset="ISO-8859-1">
   
<input type="hidden" name="_charset_" value="ISO-8859-1">
</form>

定義全局編碼格式

      通常情況下,整個應用應該保持統一的編碼格式。如果開發者需要設置應用全局的編碼格式,可以在application.conf配置文件中修改application.web_encoding屬性,配置相應的編碼。



4、Action鏈

lay中的Action鏈與Servlet API中的forward不盡相同。Play的每次HTTP請求只能調用一個Action,如果需要調用其他的Action,那麼必須將瀏覽器重定向到相應的URL。在這種情況下,瀏覽器的URL始終與正在執行的Action保持對應關係,使得後退、前進、刷新操作更加清晰。

      調用控制器中其他Action方法也可以實現重定向,框架會攔截該調用並生成正確的HTTP重定向。具體實現如下:

public class Clients extends Controller {
 
   
public static void show(Long id) {
       
Client client = Client.findById(id);
        render
(client);
   
}
 
   
public static void create(String name) {
       
Client client = new Client(name);
        client
.save();
        show
(client.id);
   
}
}

      相應的路由規則定義如下:

GET            /clients/{id}                              Clients.show
POST          
/clients                                   Clients.create

      按照定義,Action鏈的生命週期爲:

  • 瀏覽器向/clients發送POST請求;
  • 路由器調用Clients控制器中的create方法;
  • create方法直接訪問show方法;
  • Java調用被攔截,路由器逆向生成帶有id參數的URL來調用Clients.show;
  • HTTP響應重定向爲:/clients/3132;
  • 瀏覽器地址欄中URL展現爲:/clients/3132;

Action鏈
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章