03.控制器

03.控制器

在Play框架中,商業邏輯在domain model層裏進行管理,Web客戶端不能直接調用這些代碼,domain對象的功能作爲URI資源暴露出來。

客戶端使用HTTP協議提供的統一API來暗中操作這些底層的商業邏輯實現資源的維護。然而,這些domain對象到資源的映射並不是雙向注入的:它可以表示成不同級別的粒度,一些資源可能是虛擬的,某些資源的別名或許已經定義了…

這正是控制器層發揮的作用它在模型對象和傳輸層事件之間提供了粘合代碼。和模型層一樣,控制器也是純java書寫的代碼,這樣控制器層就很容易訪問和修改model對象。和http接口類似,控制器是一個面向Request/Response的程序。

控制器層減少了http和模型層之間的阻抗。

注意!

不同的策略具有不同的架構模型。一些協議可以讓你直接訪問模型對象。典型代表就是EJB和Corba協議,這種架構風格使用的是RPC(遠程過程調用),這樣的通信風格和web架構很難兼容。

SOAP協議則試着通過web訪問model對象。這是另外一個rpc類型的協議,這種情況,soap使用http作爲傳輸協議,這不是一種應用程序協議。

由於web規則並不是完全面向對象的,所以在這些協議下,針對不同的語言,需要不同的http適配器。

控制器概覽

一個controller就是一個java類,位於controller包下,是play.mvc.Controller的一個子類。

示例:

package controllers;

 

import models.Client;

import play.mvc.Controller;

 

public class Clients extendsController {

 

    public static voidshow(Long id) {

        Clientclient = Client.findById(id);

       render(client);

    }

 

    public static void delete(Longid) {

        Clientclient = Client.findById(id);

       client.delete();

    }

 

}

控制器中的每個public、static方法叫做Action(動作)。action動作方法簽名總是如下:

public static void action_name(params...);

在action方法簽名裏可以定義參數,這些參數值會被框架自動從相應的http參數裏找到。

通常情況下,一個action方法不包括return字段,方法退出是通過調用result方法完成的,在上面的示例裏,render()就是一個result方法,用於執行和顯示一個模板。

獲取http參數

一個HTTP請求包含有數據。這些數據可以從如下渠道提取:

  • URI路徑:比如/clients/1541請求, 1541就是URI範示的動態部分
  • 請求字符串:/clients?id=1541
  • 請求體:如果是通過html窗體發送的請求,請求體就包含有以x-www-urlform-encoded方式編碼的窗體數據

所有這些情況下,play都會自動進行數據提取,並存入同一個Map<String,String[]> ,這裏麪包含有所有的HTTP參數。key就是參數名name,具體從以下方式確定:

  • URI範示的動態部分名稱(和在routes文件裏指定的一樣)
  • 查詢字符串裏的name-value pair中的name
  • x-www-urlform-encoded體的內容

使用params map

params對象是一個可用於任何控制器類的變量(在play.mvc.Controller超類中定義的),這個對象包含了所有從當前http請求找到的參數。

比如:

public static void show() {

    String id =params.get("id");

    String[] names= params.getAll("names");

}

你也可以讓Play幫助完成類型轉換:

public static void show() {

    Long id =params.get("id", Long.class);

}

請等一等,你有更好的方式完成類型轉換,如下:

還可以從action方法簽名實現轉換

你可以直接從action方法簽名裏取回http參數。但Java參數的名稱必須和http參數的名稱相同:

比如下面的請求:

/clients?id=1451

一個action方法可以通過在其方法簽名裏聲明一個id參數來取回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(String anId: id) {

       System.out.println(anid);

    }

}

或是一個集合類型:

public static void show(List<Long> id) {

    for(String anId: id) {

       System.out.println(anid);

    }

}

例外情況!

如果在action方法參數裏找不到http參數對應的參數,則相應的方法參數將默認設置爲默認值(對象類型爲null,數字類型爲0)。如果能夠在action方法參數裏找到對應參數,但框架不能預測需要的java類型, play將在validationerror集合裏增加一個錯誤,並使用默認值。

高級HTTP Java綁定

簡單類型

所有本地的和通用的java類型都是自動進行綁定的:

int, long, boolean, char, byte, float, double, Integer, Long, Boolean, Char,String, Byte, Float, Double

注意:如果http請求的參數丟失或自動類型轉換失敗,對象類型將置null,簡單類型將設置爲其默認值。

Date類型

如果日期的字符串輸出匹配下面的範示,,日期對象將進行自動綁定:

  • yyyy-MM-dd’T’hh:mm:ss’Z' // ISO8601 + 時區
  • 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 voidarticlesSince(@As("dd/MM/yyyy") Date from) {

   List<Article> articles = Article.findBy("date >= ?",from);

   render(articles);

}

你也可以依照對應的語言調整日期格式:

public static voidarticlesSince(@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。請注意lang和value可以用逗號進行分隔。最爲重要的是lang的數字型參數匹配的是value的數字型參數。

如果沒有指定@As註釋,那麼play將依照訪問者的時區使用默認的日期格式。date.format configuration可以指定默認的日期格式。

Calendar日曆

日曆可以精確與日期進行綁定,除非play依照你的時區來選擇Calendar對象,還可以使用@Bind註釋。

File

在play裏,實現文件上傳非常簡單。首先使用一個multipart/form-data編碼格式的請求來傳送文件到服務器,然後使用ava.io.File類型來取回上傳的文件對象:

public static void create(String comment, Fileattachment) {

    String s3Key =S3.post(attachment);

    Document doc =new Document(comment, s3Key);

    doc.save();

    show(doc.id);

}

上傳到服務器的文件名稱和原始文件名稱一致。首先,上傳的文件會暫時保存在服務器的tmp臨時目錄,當請求結束時,該文件將被刪除。因此,你必須把這個文件複製到安全的目錄。

上傳文件的MIME類型,通常情況下在http請求的Content-typeheader裏已經 指定。然而,當從一個Web瀏覽器上傳文件時,一些不常見的文件可能不會爲其指定MIME類型。在這種情況,你可以手工使用play.libs.MimeTypes類映射文件的擴展名到某個MIME類型。

String mimeType = MimeTypes.getContentType(attachment.getName());

play.libs.MimeTypes類將查找$PLAY_HOME/framework/src/play/libs/mime-types.properties裏設定的文件擴展名來得到MIME類型。

使用Custom MIME types configuration配置,你也可添加你自己的MIME類型。

支持類型的數組或集合

所有支持類型都可以當作對象的集合或數組取回:

public static void show(Long[] id) {

    …

}

或:

public static void show(List<Long> id) {

    …

}

或:

public static void show(Set<Long> id) {

    …

}

Play也可以處理特殊的Map<String, String>綁定,比如:

public static void show(Map<String, String> client){

    …

}

下面這個查詢字符串:

?client.name=John&client.phone=111-1111&client.phone=222-2222

將綁定client變量到一個帶有兩個元素的map。第一個元素爲name:John(key/value),第二個元素爲phone: 111-1111,222-2222(同一個key,兩個值)。

POJO對象綁定

使用同樣的命名轉換規則,Play也可自動對任何model類進行綁定。

public static void create(Client client ) {

    client.save();

    show(client);

}

使用上面這個create方法,創建一個client的查詢字符串可以是下面這個樣式:

?client.name=Zenexity&[email protected]

Play將創建一個Client實例,同時把從http參數裏取得的值賦值給對象中與http參數名同名的屬性。不能明確的參數將被安全忽略,類型不匹配的也會被安全忽略。

參數綁定是通過遞歸來實現的,也就是說你可以採用如下的查詢字符串來編輯一個完整的對象圖(object graphs):

?client.name=Zenexity

&client.address.street=64+rue+taitbout

&client.address.zip=75009

&client.address.country=France

使用數組標記(即[])來引用對象的id,可以更新一列模型對象。比如,假設Client模型有一列Customer模型聲明作爲List Customer customers。爲了更新這列Customers對旬,你需要提供如下查詢字符串:

?client.customers[0].id=123

&client.customers[1].id=456

&client.customers[2].id=789

JPA 對象綁定

使用http到java的綁定,可以自動綁定一個JPA對象。

比如,在http參數裏提供了user.id字段,當play找到id字段裏,play將從數據庫加載匹配的實例進行編輯,隨後會自動把其他通過http請求提供的參數應用到實例裏,因此在這裏可以直接使用save()方法,如下:

public static void save(User user) {

    user.save(); //ok with 1.0.1

}

和在POJO映射裏採取的方式相同,你可以使用JPA綁定來編輯完整的對象圖(object graphs),唯一區別是你必須爲每個打算編輯的子對象提供id:

user.id = 1

&user.name=morten

&user.address.id=34

&user.address.street=MyStreet

定製綁定

綁定系統現在支持更多的定製化。

@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"}

        )

        DateupdatedAt

    ) {

    …

}

@As可以和所有支持它的綁定一起工作,包含你自己的綁定,比如使用ListBinder:

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

    …

}

這個綁定簡單使用逗號分隔List裏的String。

@play.data.binding.NoBinding

@play.data.binding.NoBinding註釋允許你標記一個非綁定字段,以解決潛在的安全問題。比如:

public class User extends Model {

   @NoBinding("profile") public boolean isAdmin;

    @As("dd,MM yyyy") Date birthDate;

    public Stringname;

}

 

public static void editProfile(@As("profile")User user) {

    …

}

在這種情況下, isAdmin字段絕不會被editProfile action綁定, 即使某個惡意用戶在僞造的窗體post裏寫入了user.isAdmin=true代碼。

play.data.binding.TypeBinder

@As註釋也允許你定製一個綁定。一個定製綁定必須是TypeBinder接口的子類,比如:

public class MyCustomStringBinder implementsTypeBinder<String> {

 

    public Objectbind(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

作爲選擇,你可以定義一個全局性的定製綁定,這樣的綁定將應用於相應的類型。比如,爲java.awt.Point類定製了一個綁定,如下:

@Global

public class PointBinder implementsTypeBinder<Point> {

 

    public Objectbind(String name, Annotation[] anns, String value,

    Class class) {

        String[]values = value.split(",");

        return newPoint(

           Integer.parseInt(values[0]),

           Integer.parseInt(values[1])

        );

    }

}

正如你所看到的,一個全局性綁定就是一個使用@play.data.binding.Global進行註釋的傳統綁定。通過這種方式,一個外部模塊可以將其定製的綁定應用到一個項目裏,這就爲定義一個可重用的綁定擴展提供了方法。

結果類型

一個action方法必鬚生成一個http response響應,最簡便的方法就是放出一個結果Result對象。當一個Result對象被放出時,常規的執行流程被中斷,方法退出。比如:

public static void show(Long id) {

    Client client =Client.findById(id);

    render(client);

   System.out.println("這個消息永遠不會顯示~! ");

}

render(…)方法放出一個Result對象,並停止方法執行。

返回一些文本類型的內容

renderText(…)方法放出一個簡單的Result事件,只在底層的http Response裏寫入了一些簡單的文本,比如:

public static void countUnreadMessages() {

    IntegerunreadMessages = MessagesBox.countUnreadMessages();

    renderText(unreadMessages);

}

你也可使用java標準的格式化語法來格式化輸出文本消息:

public static void countUnreadMessages() {

    IntegerunreadMessages = MessagesBox.countUnreadMessages();

   renderText("There are %sunread messages", unreadMessages);

}

返回一個JSON字符串

Play使用renderJSON(…)方法來返回一個JSON字符串。這個方法的作用是設置響應內容爲application/json,同時返回一個JSON字符串。

你可以指定你自己的JSON字符串,或把這個字符串傳遞到一個通過GsonBuilder進行串行化的對象裏,比如:

public static void countUnreadMessages() {

    IntegerunreadMessages = MessagesBox.countUnreadMessages();

    renderJSON("{\"messages\":" + unreadMessages +"}");

}

當然,如果面對一個更加複雜的對象結構,你可能會希望使用GsonBuilder來創建這個JSON字符串。

public static void getUnreadMessages() {

   List<Message> unreadMessages = MessagesBox.unreadMessages();

   renderJSON(unreadMessages);

}

當傳遞一個對象到rndoerJSON(…)方法時,如果你需要對JSON構建器進行更多控制,那麼你可以把JSON串行化並把對象輸入到定製的輸出裏。

返回一個XML字符串

和JSON方法一樣,這裏有幾個可以直接從控制器渲染XML的方法。renderXml(…) 方法返回一個內容類型設置爲text/xml的XML字符串。

在這裏,你可指定自己的xml字符串,傳遞一個org.w3c.dom.Document對象,或傳遞一個將被XStream串行化的POJO對象,比如:

public static void countUnreadMessages() {

    IntegerunreadMessages = MessagesBox.countUnreadMessages();

   renderXml("<unreadmessages>"+unreadMessages+"</unreadmessages>");

}

當然,你也可以使用org.w3c.dom.Document對象:

public static void getUnreadMessages() {

    DocumentunreadMessages = MessagesBox.unreadMessagesXML();

   renderXml(unreadMessages);

}

返回二進制內容

向用戶返回一個存儲在服務器上的二進制文件需要使用renderBinary()方法。比如你有一個User對象,並且有一個play.db.jpa.Blob的圖片屬性。這時,就可以使用如下語句在一個控制器方法里加載這個模型對象,並使用對應的MIME類型渲染這個對象裏的圖片:

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);

}

作爲附件下載文件

通過設置http header,可以指示web瀏覽器把一個二進制響應當作“附件”來對待,這樣就可以在web瀏覽器把文件下載到用戶的電腦。爲了完成這個任務,可以把一個文件的名稱傳遞給renderBinary方法,並作爲它的參數,這時play會自動設置Content-Disposition響應header。比如,當上面示例裏的User模型作爲一個photoFileName屬性時:

renderBinary(binaryData, user.photoFileName);

執行一個模板

如果生成的內容,你應該使用模板來生成response內容。

public class Clients extends Controller {

 

    public staticvoid index() {

       render();   

    }

}

模板名稱且根據play約定自動進行推斷。默認模板路徑就是控制器和Action的名稱。

在上面這個示例裏調用的模板路徑和名稱如下:

app/views/Clients/index.html

向模板作用域添加數據

通常情況下,模板是需要數據的。你可以使用renderArgs對象添加數據到模板作用域:

public class Clients extends Controller {

 

    public staticvoid show(Long id) {

        Clientclient = Client.findById(id);

        renderArgs.put("client",client);

       render();   

    }

}

在模板執行期間,client變量將被定義。

比如:

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

更加簡潔的形式添加數據到模板作用域

使用render(…)方法的參數,可以把數據直接傳遞到模板:

public static void show(Long id) {

    Client client =Client.findById(id);

   render(client);   

}

在這種情況下,模板可以訪問的變量與java局部變量的名稱完全相同。當然,還可通過這種方式傳遞多個變量:

public static void show(Long id) {

    Client client =Client.findById(id);

    render(id,client);   

}

非常重要!

你只能通過這種方式向模板傳遞局部變量

指定其他模板

如果不想使用默認的模板,可以使用renderTemplate(…)方法指定自己的模板文件,但模板名稱要作爲第一個參數:

如:

public static void show(Long id) {

    Client client =Client.findById(id);

   renderTemplate("Clients/showClient.html",id, client);   

}

跳轉到其他URL

redirect(…)方法放出一個跳轉事件, 用於切換生成一個http跳轉響應。

public static void index() {

   redirect("http://www.zenexity.fr");

}

Action

play和Servlet API的forward不同,一個http請求只能調用一個action。如果你想要調用其他的action,就必須跳轉瀏覽器到能夠調用這個action的URL。通過這種方式,瀏覽器URL就總是和被執行的Action保持一致,因此對瀏覽器的back/forward/refresh管理就非常容易。

在java代碼裏,通過調用其他action方法,就可以實現發送一個跳轉響應到任何action裏,比如:

public class Clients extends Controller {

 

    public staticvoid show(Long id) {

        Clientclient = Client.findById(id);

       render(client);

    }

 

    public staticvoid create(String name) {

        Clientclient = new Client(name);

       client.save();

       show(client.id);

    }

}

上面的示例在使用下面這兩條路由的情況下:

GET   /clients/{id}           Clients.show

POST  /clients                Clients.create

  • 瀏覽器發送一個POST到/clients URL
  • 路由調用Clients控制器的create action
  • action方法直接調用show action方法
  • Java調用被中斷,反向路由生成器創建一個帶id參數的Clients.show方法調用的URL
  • HTTP響應爲: 302 Location:/clients/3132
  • 瀏覽器隨後發佈GET /clients/3132

定製web編碼

play着重使用UTF-8編碼,但某些情況下必須使用其他編碼。

爲當前response定製編碼

要爲當前response改變編碼,可以控制器裏參照下面的方法進行定製:

response.encoding = "ISO-8859-1";

當傳遞一個與服務器默認編碼不同的窗體時,你應該在窗體裏使用encoding/charset兩次,兩次都用在accept-charset屬性指定上,而且是用在一個名稱叫_charset_的特殊隱藏窗體上。accept-charset屬性會告訴瀏覽器在傳遞窗體裏要使用哪種編碼,form-field _charset_ 則告訴play需要採用哪個編碼進行處理:

<form action="@{application.index}"method="POST" accept-charset="ISO-8859-1">

    <inputtype="hidden" name="_charset_"value="ISO-8859-1">

</form>

爲整個應用程序定製編碼

配置application.web_encoding 用於指定play與瀏覽器進行通信時需要採用哪種編碼。

攔截器

在控制器裏,可以定義攔截器方法。攔截器將被控制器類及其後代的所有action調用。可以利用這個特點爲所有的action定義一些通用的提前處理代碼:比如對用戶進行驗證、加載請求作用域信息等…

這些方法必須是static的,但不能是public的,並且使用有效的攔截註釋:

@Before

用@Before註釋的方法將在這個控制器的每個action被調用前執行。因此,可以利用這個註釋創建一個安全檢查方法:

public class Admin extends Controller {

 

    @Before

    static voidcheckAuthentification() {

        if(session.get("user") == null)login();

    }

 

    public staticvoid index() {

       List<User> users = User.findAll();

       render(users);

    }

    …

}

如果不希望@Before方法中斷所有的action調用,可以指定一個需要排除的Action列表:

public class Admin extends Controller {

 

    @Before(unless="login")

    static voidcheckAuthentification() {

       if(session.get("user") == null) login();

    }

 

    public staticvoid index() {

       List<User> users = User.findAll();

       render(users);

    }

 

    …

}

如果希望@Before方法中斷列表中的action調用,可以使用only參數:

public class Admin extends Controller {

 

    @Before(only={"login","logout"})

    static voiddoSomething() { 

        … 

    }

    …

}

unlessonly 參數也可用於@After, @Before和@Finally註釋。

@After

用@After註釋的方法將在每個action調用執行完後執行。

public class Admin extends Controller {

 

    @After

    static voidlog() {

        Logger.info("Action executed...");

    }

 

    public staticvoid index() {

       List<User> users = User.findAll();

       render(users);

    }

 

    …

}

@Catch

用@Catch註釋的方法將在其他action方法拋出指定異常時執行。這個被拋出的異常將作爲參數傳遞給@Catch註釋的方法。

public class Admin extends Controller {

       

   @Catch(IllegalStateException.class)

    public staticvoid logIllegalState(Throwable throwable) {

       Logger.error("Illegal state %s…", throwable);

    }

   

    public staticvoid index() {

       List<User> users = User.findAll();

        if(users.size() == 0) {

            throw newIllegalStateException("Invalid database - 0 users");

        }

       render(users);

    }

}

和普通的java異常處理一樣,你可以捕獲一個超類來捕獲更多的異常類型。如果不只一個捕獲方法,可以指定他們的priority(優先級)參數,以確定執行順序(1是最高優先級)。

public class Admin extends Controller {

 

    @Catch(value =Throwable.class, priority = 1)

    public staticvoid logThrowable(Throwable throwable) {

        // Customerror logging…

       Logger.error("EXCEPTION %s", throwable);

    }

 

    @Catch(value =IllegalStateException.class, priority = 2)

    public staticvoid logIllegalState(Throwable throwable) {

        Logger.error("Illegalstate %s…", throwable);

    }

 

    public staticvoid index() {

       List<User> users = User.findAll();

       if(users.size() == 0) {

            thrownew IllegalStateException("Invalid database - 0 users");

        }

        render(users);

    }

}

@Finally

用@Finally註釋的方法總是在這個控制器裏的每個action調用執行後被執行。用@Finally註釋的方法,不管action調用是否成功也會執行。

public class Admin extends Controller {

 

    @Finally

    static voidlog() {

       Logger.info("Response contains : " + response.out);

    }

 

    public staticvoid index() {

       List<User> users = User.findAll();

       render(users);

    }

    …

}

如果 @Finally註釋的方法帶有一個Throwable類型的參數,那麼有異常發生時,異常將會傳送到方法裏面來。

public class Admin extends Controller {

 

    @Finally

    static voidlog(Throwable e) {

        if( e == null ){

           Logger.info("action call was successful");

        } else{

           Logger.info("action call failed", e);

        }

    }

 

    public staticvoid index() {

       List<User> users = User.findAll();

        render(users);

    }

    …

}

控制器繼承

如果一個控制器類是其他控制器類的子類,那麼攔截器也會按照繼承順序應用於相應層級的子類。

使用@With註釋添加更多的攔截器

Because Java does not allow multiple inheritance, it canbe very limiting to rely on the Controller hierarchy to apply interceptors. Butyou can define some interceptors in a totally different class, and link themwith any controller using the @With annotation.由於java不允許多繼承,通過控制器繼承特點來應用攔截器就受到極大的限制。但是我們可以在一個完全不同的類裏定義一些攔截器,然後在任何控制器裏使用@With註釋來鏈接他們。

比如:

public class Secure extends Controller {

   

    @Before

    static voidcheckAuthenticated() {

       if(!session.containsKey("user")) {

           unAuthorized();

        }

    }

}   

另一個控制器:

@With(Secure.class)

public class Admin extends Controller {

   

    …

}

Session和Flash作用域

爲了在多個http請求間保存數據,你可以把這些數據存入Session或Flash作用域。存儲在Session裏的數據在整個用戶session期間是可用的,存儲在Flash裏的數據只在下一個請求裏可用。

明白把session和flash數據通過Cookie機制存儲在每個請求客戶端電腦裏而不是存儲在服務器上這點很重要,這就確定了數據大小不能超過最多4kb,而且只能存儲字符串類型的值。

當然,play已經爲cookie指派了一個安全key,因此客戶端不能編輯cookie數據(否則cookie將無效)。因此play框架不打算把session用作緩存,如果你需要緩存一些與session相關的數據,你可以使用Play內建的緩存機制,並用session.getId()key來保存某個特定用戶session相關的數據,比如:

public static void index() {

    List messages =Cache.get(session.getId() +"-messages", List.class);

    if(messages ==null) {

        // Cachemiss

        messages =Message.findByUser(session.get("user"));

       Cache.set(session.getId() + "-messages", messages,"30mn");

    }

   render(messages);

}

當你關閉瀏覽器裏,session就過期了。除非對application.session.maxAge進行了配置。

play裏的緩存和傳統的Servlet HTTP session對象的概念完全不同。千萬不要認爲這些緩存了的對象會永遠在緩存裏。因此,play會強迫你去處理緩存丟失的情況,以防止緩存數據丟失,這樣就保證了應用程序是完全無狀態的。

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