日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

26Play框架教程2学习笔记

發(fā)布時間:2023/12/14 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 26Play框架教程2学习笔记 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
Play框架教程2學(xué)習(xí)筆記

文章目錄

  • 1 play框架01
    • 1.1 概述
    • 1.2 特性
      • 1.2.1 無縫集成現(xiàn)有開發(fā)環(huán)境
      • 1.2.2 熱重載和修改Bug
      • 1.2.3 簡單的無狀態(tài)MVC架構(gòu)
      • 1.2.4 HTTP到代碼的映射
      • 1.2.5 高效的模板引擎
      • 1.2.6 內(nèi)置JPA支持
      • 1.2.7 Full Stack應(yīng)用框架
      • 1.2.8 Play的特性總結(jié)
  • 2 play框架02
    • 2.1 app目錄
    • 2.2 conf目錄
    • 2.3 public 的目錄
    • 2.4 lib目錄
    • 2.5 project目錄
    • 2.6 target目錄
  • 3 play框架03
    • 3.1 安裝Play
    • 3.2 創(chuàng)建項目
    • 3.3 運行程序
    • 3.4 配置數(shù)據(jù)庫
    • 3.5 補充
    • 3.6 將play項目導(dǎo)入myeclipse
  • 4 play框架04
    • 4.1 路由
      • 4.1.1 HTTP方法
      • 4.1.2 URI表達(dá)式
      • 4.1.3 定義Java調(diào)用
      • 4.1.4 404作為Action
      • 4.1.5 指定靜態(tài)參數(shù)
      • 4.1.6 變量和腳本
      • 4.1.7 staticDir:mapping
      • 4.1.8 staticFile:mapping
      • 4.1.9 虛擬主機
    • 4.2 逆向生成URL
    • 4.3 設(shè)置content type
    • 4.4 HTTP內(nèi)容協(xié)商
      • 4.3.1 在HTTP頭中設(shè)置content type
      • 4.3.2 自定義格式
    • 4.5 關(guān)于REST
  • 5 play框架05
    • 5.1 概述
    • 5.2 獲取HTTP參數(shù)
      • 5.2.1 使用Map參數(shù)
      • 5.2.2 高級HTTP綁定
        • 5.2.2.1 簡單類型
        • 5.2.2.2 日歷類型
        • 5.2.2.3 文件類型
        • 5.2.2.4 數(shù)組和集合類型
        • 5.2.2.5 POJO對象綁定
      • 5.2.3 JPA對象綁定
      • 5.2.4 自定義綁定
    • 5.3 結(jié)果返回
      • 5.3.1 返回文本內(nèi)容
      • 5.3.2 返回JSON字符串
      • 5.3.3 返回XML字符串
      • 5.3.4 返回二進(jìn)制內(nèi)容
      • 5.3.5 下載附件功能
      • 5.3.6 執(zhí)行模板
      • 5.2.7 為模板作用域添加數(shù)據(jù)
      • 5.3.8 更簡單方式
      • 5.3.9 指定其他模板進(jìn)行渲染
      • 5.3.10 重定向URL
      • 5.3.11 自定義Web編碼
    • 5.4 Action鏈
    • 5.5 攔截器
      • 5.5.1 @Before
      • 5.5.2 @After
      • 5.5.3 @Catch
      • 5.5.4 @Finally
      • 5.5.5 使用@with注解增加更多攔截器
    • 5.6 Session和Flash作用域
  • 6 play框架06
    • 6.1 模板語法
      • 6.1.1 表達(dá)式(expression):${…}
      • 6.1.2 標(biāo)簽(tag): #{tagName /}
      • 6.1.3 引用(action):@{…}或者@@{…}
      • 6.1.4 國際化(messages):&{…}
      • 6.1.5 注釋(comment):
      • 6.1.6 腳本(script): %{…}%
    • 6.2 模板繼承
  • 7 play框架07
    • 7.1 屬性模擬
    • 7.2 數(shù)據(jù)庫配置
    • 7.3 數(shù)據(jù)持久化
    • 7.4 無狀態(tài)模型
  • 8 play框架08
    • 8.1 Job實現(xiàn)
    • 8.2 Bootstrap Job
      • 8.2.1 應(yīng)用啟動
      • 8.2.2 應(yīng)用停止
    • 8.3 Scheduled Job
    • 8.4 Job的直接調(diào)用
  • 9 play框架的請求處理流程
    • 9.1 PlayHandler
    • 9.2 Invoker與Invocation
    • 9.3 Result類
    • 9.4 Template類
  • 10 play框架的攔截器
  • 11 play源碼分析
    • 11.1總體流程
    • 11.2 Start流程
    • 11.3 啟動HTTP服務(wù)

1 play框架01

? play框架01–介紹

1.1 概述

Play框架顛覆了臃腫的企業(yè)級Java EE規(guī)范,以Restful為目標(biāo)并專注于開發(fā)效率,是Java敏捷開發(fā)的最佳參考方案。

開發(fā)者只要具備Java以及數(shù)據(jù)庫的相關(guān)基礎(chǔ)知識就可以輕松上手,從而讓W(xué)eb應(yīng)用開發(fā)變得更加容易,提高項目催化速度。

作為Full Stack的Java Web應(yīng)用框架,Play包括了所有開發(fā)中涉及的領(lǐng)域:NIO應(yīng)用容器,無狀態(tài)MVC模型,Hibernate數(shù)據(jù)持久化,

Groovy模板引擎,以及建立Web應(yīng)用所需要的各種工具類。需要注意的是,這里雖然使用了Groovy,但只是將其作為頁面模板語言,

和Freemaker、Velocity使用自己定義的語言是同樣的道理。

Groovy的成熟以及它和Java的相似性決定了采用Groovy遠(yuǎn)遠(yuǎn)好于定義自己的模板語言。

1.2 特性

1.2.1 無縫集成現(xiàn)有開發(fā)環(huán)境

Play1.x是基于Java的Web開發(fā)框架,允許開發(fā)者使用自己常用的集成開發(fā)工具(如Eclipse)和類庫。

如果讀者已經(jīng)以Java作為開發(fā)方向,那么無須進(jìn)行開發(fā)語言、IDE或者類庫的切換,要做的就是在更加高效的Java環(huán)境中開發(fā)Web應(yīng)用。

1.2.2 熱重載和修改Bug

Java在過去因為開發(fā)效率低下而臭名昭著,主要是因為其重復(fù)和乏味的編譯-打包-部署周期。因此在設(shè)計框架的時候?qū)@些因素都進(jìn)行了重新考量,目標(biāo)是讓Play應(yīng)用的開發(fā)過程變得更加高效。

Play框架會自動編譯Java源文件,而不用重新啟動Web服務(wù)器將代碼熱加載至JVM。這樣做的好處是:當(dāng)代碼修改完保存后,框架自動編譯并重載修改后的類,只需刷新瀏覽器就可以查看更改的結(jié)果,就像在LAMP或者Rails環(huán)境中開發(fā)一樣。另外一個好處是:開發(fā)的時候甚至可以只用簡單的文本編輯器,而不使用功能完備的Java IDE進(jìn)行開發(fā)。

1.2.3 簡單的無狀態(tài)MVC架構(gòu)

一端是數(shù)據(jù)庫,另一端是Web瀏覽器,為什么我們需要在這兩者之間保存狀態(tài)?

有狀態(tài)并且基于組件的Java Web框架能夠更加容易地保存頁面狀態(tài),但這同樣帶來了很多其他的問題:如果用戶在新的瀏覽器窗口中重新打開應(yīng)用會發(fā)生什么?用戶按了后退按鈕又會是什么結(jié)果?

無共享架構(gòu)是很多Web應(yīng)用框架所提倡的(ROR,Django等)。由于瀏覽器變得越來越強大,我們并不需要技巧性地構(gòu)建HTTP模型來創(chuàng)建偽造的狀態(tài),只需在客戶端使用Ajax或者離線存儲技術(shù)就可以很容易地解決狀態(tài)問題。無共享架構(gòu)的另一優(yōu)勢是使頁面的呈現(xiàn)更加平滑,更容易地實現(xiàn)局部頁面更新(或者漸進(jìn)式的頁面處理流程)。

1.2.4 HTTP到代碼的映射

如果讀者使用過其他的Java Web框架(比如說Struts)可能會發(fā)現(xiàn),這些框架的底層實現(xiàn)其實是對HTTP協(xié)議做了進(jìn)一步封裝,

所以它們提供的Java API和自身的理念會讓人覺得很不自然。Play框架在設(shè)計過程中換了一種思維方式,

即Web應(yīng)用框架也應(yīng)該提供完整、直接的方式去訪問HTTP————這也是Play框架和其他Java Web框架最根本的差異。

HTTP,Request/Response模式,Rest架構(gòu)風(fēng)格,HTTP內(nèi)容協(xié)商(Content–type negotiation),URI等等,

所有這些都是Play框架的主要概念。如果用戶需要將URI綁定到指定的Java方法調(diào)用,只需要在路由文件中以如下方式進(jìn)行配置:

GET /clients/{id} Clients.show

如果Ajax,REST以及管理頁面之間的“前進(jìn)/后退”操作是日常開發(fā)中需要頻繁考慮的需求,

那么Play框架無疑是最佳的選擇,因為針對這些問題它都提供了非常優(yōu)秀的解決方案。

Play是一個完全無狀態(tài)的,只面向請求/響應(yīng)的框架,所有HTTP請求都具有相同的處理流程:

? 1.框架接收HTTP請求。

? 2.路由組件找到最匹配的規(guī)則,接受并處理請求,隨后調(diào)用相應(yīng)的Action方法。

? 3.執(zhí)行Action中的應(yīng)用代碼。

? 4.如果需要生成復(fù)雜的視圖,使用模板文件進(jìn)行渲染。

? 5.Action方法的返回結(jié)果(HTTP響應(yīng)代碼以及內(nèi)容)被轉(zhuǎn)換為HTTP響應(yīng)。

1.2.5 高效的模板引擎

? 也許讀者已經(jīng)深深地感受到了JSP和表達(dá)式語言背后的理念,但是為什么在創(chuàng)建標(biāo)簽庫的時候需要如此多的配置文件?為什么不能直接訪問底層的模型對象?JSP中太多的限制確實讓開發(fā)者感到失望,受JSP啟發(fā)又不被其約束,Play框架提供了自定義的模板引擎機制。

開發(fā)者再也不需要編寫這些令人厭倦的代碼了:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %><c:choose><c:when test="${emails.unread != null && fn:size(emails.unread)}">You have ${fn:size(emails.unread)} unread email(s)!</c:when><c:otherwise>You have no unread emails!</c:otherwise> </c:choose>

相信開發(fā)者更傾向于用以下方式來書寫模板代碼:

You have ${emails.unread ?: 'no'} ${emails.unread?.pluralize('email')} !

Play模板引擎使用的表達(dá)式語言為Groovy,它提供了與Java一致的語法。

Play主要使用模板機制來渲染HTML,當(dāng)然也可以生成其他的文檔格式,比如e-mail messages,JSON等等。

1.2.6 內(nèi)置JPA支持

? JPA(Java Persistence API)是Java中最簡潔的對象關(guān)系映射(object-relational mapping即ORM)API。

如果讀者以前了解或者使用過JPA,就會發(fā)現(xiàn)與其他框架相比,在Play中使用會更加方便。這是因為Play框架對其做了進(jìn)一步封裝,

不需要任何配置,Play會自動開啟JPA實體管理器(EM),一旦代碼被調(diào)用就自動進(jìn)行持久化操作。

此外,實體如果繼承Play提供的play.db.jpa.Model類,操作代碼將會更加簡潔,更加美觀:

public static void messages(int page) {User connectedUser = User.find("byEmail", connected()).first();List<Message> messages = Message.find("user = ? and read = false order by date desc",connectedUser).from(page * 10).fetch(10);render(connectedUser, messages); }

1.2.7 Full Stack應(yīng)用框架

Play框架的最初設(shè)計受到實際Java Web開發(fā)的啟發(fā),包含了所有創(chuàng)建主流Web應(yīng)用所需要的工具:

  • 通過JDBC提供關(guān)系數(shù)據(jù)庫支持。

  • 使用Hibernate進(jìn)行對象關(guān)系映射(JPA)。

  • 使用分布式Memcached集成緩存支持。

  • 以JSON或者XML的形式提供web service支持。

  • 基于OpenID的分布式用戶信息驗證。

  • Web應(yīng)用可以部署在任何應(yīng)用服務(wù)器上(Tomcat,Jboss,GAE,Cloud等)。

  • 圖像處理API(驗證碼)。

    此外Play還提供了很多實用的模塊。開發(fā)者可以結(jié)合這些模塊構(gòu)建Web應(yīng)用,

? 這使得我們可以以更加簡單,更加直接的方式重用Java代碼、模板以及靜態(tài)資源(比如JavaScript和CSS文件)。

1.2.8 Play的特性總結(jié)

  • 自動編譯和重載:當(dāng)編輯Java文件并保存后,刷新瀏覽器就能立即查看結(jié)果。

    使用Play開發(fā)不需要手動編譯、部署以及重新啟動Web服務(wù)器等操作。

  • 無狀態(tài)模型:Play是真正的無共享框架,為REST而準(zhǔn)備。它可以將同一個應(yīng)用的多個實例分別部署在多臺服務(wù)器上,因而擴展性非常強。

  • 高效的模板引擎:基于表達(dá)式語言Groovy的清晰模板引擎,提供了模板的繼承、導(dǎo)入以及標(biāo)簽自定義等功能。

  • 快速解決錯誤:當(dāng)錯誤發(fā)生時,Play會在瀏覽器中顯示出錯代碼塊并提示問題發(fā)生的確切位置。

  • Full Stack:提供創(chuàng)建Web應(yīng)用所需的全部功能,集成了Hibernate、OpenID、Memcached等第三方類庫。

  • 純Java:Play采用Java編寫代碼,可以方便地使用任何Java類庫,并且能夠非常好地和Eclipse、Netbeans等IDE集成,只需通過命令生成匹配的項目文件即可。

  • 基于非阻塞的IO模型:允許創(chuàng)建基于長輪詢和WebSocket的主流Web應(yīng)用。

  • 有趣并且高效:省去了Java應(yīng)用重啟的時間,提高了應(yīng)用的開發(fā)效率。

  • 2 play框架02

    ? play框架02–細(xì)說目錄結(jié)構(gòu)

    play的目錄結(jié)構(gòu)制作的相當(dāng)精簡,以下是從play官網(wǎng)截下的圖片:

    2.1 app目錄

    app目錄是代碼目錄,包含了所有的Java或者Scala的源碼,一般的“hello-world”sample程序都含有controllers、models、和views三個目錄,分別對應(yīng)MVC三層結(jié)構(gòu)中的:C、M和V;我想這大家都和清楚,大家還可以根據(jù)自己的項目需要創(chuàng)建其他的目錄,

    例如utils、dao等等。例如以下:

    如果有需要,你還可以建一個名為“assets”的目錄,里面可以放LESS或者CoffeeScript源文件。

    注意:這些controllers、models和views等目錄可以隨著你項目的需要而改變,

    例如:你可以寫成com.yourcompany.controllers、com.yourcompnay.model和com.yourcompany.views而不必非得寫成controllers、models和views。

    2.2 conf目錄

    在這個目錄里,放置的都是這個應(yīng)用的一些配置文件信息,有兩個主要的文件:

    一個是application.conf:意思很明顯,就是整個應(yīng)用的配置信息,里面會有一些配置的參數(shù)。

    包括數(shù)據(jù)庫鏈接中數(shù)據(jù)源的信息填寫,日志打印的級別等信息等等,還可以自定義一些參數(shù)。

    注意:在conf中,play默認(rèn)定義的有:數(shù)據(jù)庫信息、應(yīng)用信息(名字、 Secret key、語言等)、日志;

    這三塊兒的信息,在conf中直接改后,效果會在應(yīng)用程序中直接出現(xiàn)。

    假如你想一用conf中自定義的配置參數(shù):例如上圖中的阿里云相關(guān)的信息,你需要在application.conf中定義之后,在程序中使用

    Play.configuration.getString("oss.access_id").getOrElse("diSnug5q4zb9y2mq")

    來調(diào)用。實際上某人的那三塊信息也是這么來調(diào)用的。

    假如你在application.conf中不想定義過多的自定義信息,你也可以寫一個自定義的conf文件,然后在application.conf中引用(include “fileName.conf”)如下:

    routes:路由。非常重要的部分!使用方法非常簡單,在這里定義你需要的rest接口,然后接口后面對應(yīng)的處理函數(shù)。如下圖:

    2.3 public 的目錄

    這里放置的都是前端頁面相關(guān)的信息,例如js、css、json文件、圖片等等。

    這些目錄文件的名字是可以改的,但是引用的時候需要注意目錄名字。包括public的名字也是可以改的。前端頁面中需要其中的靜態(tài)文件的話,需要再routes中添加:

    然后在前端需要靜態(tài)文件的地方這么引用:

    這里就是用的public目錄下images目錄中的靜態(tài)文件。

    2.4 lib目錄

    如果之前你是做J2EE項目的,這個目錄你一定清楚,這就是放置其他依賴包的地方。

    (當(dāng)然如果Maven有依賴鏈接,盡量用Maven的依賴鏈接)

    build.sbt file:

    這個文件是整個項目添加依賴包的地方,所有的依賴都寫在這里。

    如果你是J2EE開發(fā)者的話,你一定知道Maven的pom.xml文件,在這里,build.sbt文件就相當(dāng)于pom.xml的文件。

    2.5 project目錄

    這個目錄包含了sbt構(gòu)建之后的東西:

    1、pulgins.sbt:插件sbt

    2、build.properties:包含了sbt的版本。

    2.6 target目錄

    target目錄包含了應(yīng)用編譯之后的東西,就是編譯后的可執(zhí)行文件。

    3 play框架03

    ? play框架03–創(chuàng)建項目

    3.1 安裝Play

    從下載頁面下載最新的二進(jìn)制包,然后在你喜歡的地方解壓它。

    ? 如果你用的是Windows,最好避免在路徑中混入空格。比如c:\play就是個比c:\Documents And Settings\user\play更好的選擇。

    為了方便操作,你需要添加Play文件夾到你的系統(tǒng)路徑中。這樣你就不需要在play命令前面敲一大通路徑名了。

    要想檢查安裝是否成功,打開一個新的命令行窗口,敲下play;應(yīng)該會出來play的基本使用幫助。

    3.2 創(chuàng)建項目

    現(xiàn)在Play已經(jīng)安好了,是時候開始寫博客應(yīng)用。創(chuàng)建一個Play應(yīng)用非常簡單,僅需要play命令行工具。之后會生成Play應(yīng)用的基本架構(gòu)。

    打開一個新的命令行并敲入:

    ~$ play new yabe

    它會提醒輸入應(yīng)用的全名。輸入yabe
    play new命令創(chuàng)建了一個新的文件夾yabe/外加一系列文件和文件夾。其中包括下面各部分:

    ? app/ 包括應(yīng)用的核心,劃分為models,controllers和views文件夾。它也可以包括其他Java的包。這是.java源代碼文件所在之處。

    ? conf/ 包括所有的應(yīng)用配置文件,特別是主application.conf文件,路由定義文件和用于國際化的信息文件。

    ? lib/ 包括所有可選的Java庫,比如標(biāo)準(zhǔn)的.jar。

    ? public/ 包括所有可以公開的資源,比如Javascript文件,樣式表和圖片。

    ? test/ 包括所有的應(yīng)用測試。測試可以是Java的JUnit測試或者Selenium測試。

    因為Play只使用UTF-8編碼,故所有的文本文件都需要使用UTF-8編碼。確保你的文本編輯器已經(jīng)做了相應(yīng)的配置。

    如果你開發(fā)過Java應(yīng)用,你可能會奇怪.class文件到哪兒去了。答案是……沒有.class文件了:Play并不使用任何class文件;相反它直接處理Java源代碼。實際上我們使用Eclipse的編譯器來即時編譯Java源代碼
    這導(dǎo)致了開發(fā)過程中的兩點重要的改進(jìn)。

    ? 第一個,Play會自動監(jiān)測Java源代碼的改變并在運行時自動重載。

    ? 第二個,當(dāng)一個Java異常發(fā)生時,Play能向你展示更好的錯誤報告 - 帶對應(yīng)的源代碼的哦~

    事實上Play在應(yīng)用的tmp/文件夾下有字節(jié)碼的緩存,但只用于加速重新啟動項目的過程。

    如果需要,你可以用play clean清空緩存。

    3.3 運行程序

    現(xiàn)在可以測試一下新建立的程序了。回到命令行窗口,在yabe/目錄下輸入play run命令。

    Play框架將載入程序,啟動Web服務(wù)器并監(jiān)聽9000端口。

    打開瀏覽器鍵入http://localhost:9000,程序顯示了一個缺省的歡迎頁。

    現(xiàn)在我們來看看這個頁面是怎樣顯示的。

    程序的主入口配置在conf/routes文件里。這個文件定義了程序所有可訪問的URL。

    打開routes文件,會看到第一個“route”:

    GET / Application.index

    它告訴Play,當(dāng)服務(wù)器收到來自于/路徑的GET請求時要調(diào)用Application.index的方法。

    在這個程序中,Application.index是controllers.Application.index簡寫,因為controllers包是隱式附加的。

    在創(chuàng)建一個標(biāo)準(zhǔn)Java程序時,通常會定義一個入口方法,比如:

    public static void main(String[] args) { ... }

    Play程序則有多個入口方法,每個URL就有一個。這些方法稱為action方法。定義Action方法的類稱為controller。

    看看什么是controller。打開yabe/app/controllers/Application.java源文件:

    package controllers; import play.mvc.*; public class Application extends Controller { public static void index() { render(); } }

    controller 類繼承于play.mvc.Controller類,這個類提供了許多controller需要的方法,比如在index action中的render方法。

    index action定義為public static void,因為controller類不需要實例化和返回值。

    index action很簡單,只是調(diào)用了render()方法來通知Play渲染模板。

    使用模板是返回HTTP響應(yīng)的一個最通用的方式。

    模板是在/app/views 目錄下的簡單文本文件。

    因為這里沒有指定一個模板,index action會使用一個默認(rèn)的模板:Application/index.html。

    打開/yabe/app/views/Application/index.html:

    #{extends 'main.html' /}#{set title:'Home' /} #{welcome /}

    在這個模板中,只有Play tag,與JSP tag類似,#{welcome /} tag會在瀏覽器中生成歡迎信息。

    #{extends /} tag 表示這個模板繼承于main.html這個模板。模板繼承可用來創(chuàng)建復(fù)雜的web也并重用公共部分。

    打開/yabe/app/views/main.html模板:

    <!DOCTYPE html> <html> <head> <title>#{get 'title' /}</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link rel="stylesheet" type="text/css" media="screen" href="@{'/public/stylesheets/main.css'}" /> <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}" /> </head> <body> #{doLayout /} </body> </html>

    #{doLayout /}tag表示index.html插入內(nèi)容的地方。

    試著編輯controller類來看看Play怎么自動加載它。

    打開yabe/app/controllers/Application.java,刪除render()后的分號,讓它出錯,就像這樣:

    public static void index() { render() }

    然后到瀏覽器刷新這個頁面,Play會檢測源文件變更并試著加載程序controller,

    但是因為controller有錯誤,所以在瀏覽器中顯示一個編譯錯誤。

    把剛才的錯誤修改正確,在編輯模板,打開yabe/app/views/Application/index.html覆蓋歡迎消息。

    #{extends 'main.html' /} #{set title:'Home' /} <h1>A blog will be here</h1>

    在瀏覽器刷新這個頁面。

    3.4 配置數(shù)據(jù)庫

    在開始寫代碼之前還要多做一件事。作為博客引擎,我們需要一個數(shù)據(jù)庫。為了便于與開發(fā),Play內(nèi)置了一個叫做H2的數(shù)據(jù)庫。

    當(dāng)然如果需要,我們也可以切換到一個更加健壯的數(shù)據(jù)庫。

    你可以選擇設(shè)置數(shù)據(jù)時存儲在內(nèi)存中,還是在文件系統(tǒng)中(這樣即使你重新啟動,你的數(shù)據(jù)也會保留)。
    在一開始,我們將對應(yīng)用模型做許多測試和改動。因此,最好選擇存儲在內(nèi)存中,這樣每次啟動,都不會跟舊數(shù)據(jù)有任何牽連。
    打開yabe/app/application.conf,解除這一行的注釋:

    db=em

    正如你在注釋中看到的一樣,你可以冗余的配置任何JDBC數(shù)據(jù)庫,甚至配置鏈接池。

    現(xiàn)在回到瀏覽器并刷新歡迎頁面。Play將自動啟動數(shù)據(jù)庫。

    檢查下面一行是否出現(xiàn)在應(yīng)用日志中:

    INFO ~ Connected to jdbc:h2:mem:play

    3.5 補充

    如果運行play run 命令出現(xiàn)下面提示:

    解決辦法:
    找到play\framework\build.bat 修改

    java -Xms512M -Xmx1024M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M %DEBUG_PARAM% %JAVA_OPTS% -Dfile.encoding=UTF-8 -Dinput.encoding=Cp1252 -Dplay.version="%PLAY_VERSION%" -Dsbt.ivy.home="%~dp0..\repository" -Dplay.home="%~dp0." -Dsbt.boot.properties="%fp%sbt/sbt.boot.properties" %PLAY_OPTS% -jar "%~dp0sbt\sbt-launch.jar" %*

    java -XX:+CMSClassUnloadingEnabled %DEBUG_PARAM% -Dfile.encoding=UTF8 -Dplay.version="%PLAY_VERSION%" -Dsbt.ivy.home="%~dp0..\repository" -Dplay.home="%~dp0." -Dsbt.boot.properties="file:///%p%sbt/sbt.boot.properties" -jar "%~dp0sbt\sbt-launch.jar" %*

    3.6 將play項目導(dǎo)入myeclipse

    前提:已安裝play并配置了環(huán)境變量

    第一步 :打開你的項目將下面幾項刪除(沒有就跳過)

    第二步:在cmd中來到項目的路徑下(cd,不是來到項目里,而是項目名前一級目錄),

    ? 然后輸入play eclipsify +項目名

    第三步:導(dǎo)入

    4 play框架04

    4.1 路由

    Play框架中的路由器是負(fù)責(zé)將傳入的HTTP請求映射為Action調(diào)用(即控制器中被聲明為public static void的方法)的組件。

    HTTP請求被MVC框架視為事件,其主要包括以下兩塊內(nèi)容:

    ? 。請求路徑(比如/clients/1542,/photos/list),其中可以包含查詢字符串。
    ? 。HTTP方法(GET,POST,PUT,DELETE)。

    Play路由器使用的配置文件為conf/routes,該文件列出了應(yīng)用需要的所有路由規(guī)則。

    每條路由由HTTP方法和與Java調(diào)用相關(guān)聯(lián)的URI組成。以下是路由配置的例子:

    GET /clients/{id} Clients.show

    ? 路由配置總是從HTTP方法開始,URI作為中間部分,最后的元素是Java調(diào)用。

    在路由文件中可以使用#進(jìn)行注釋:

    # Display a client GET /clients/{id} Clients.show

    4.1.1 HTTP方法

    ? HTTP協(xié)議支持以下所列的方法,用于指定客戶請求服務(wù)器的動作,其中GET和POST是最為常用的兩種方法:

    ? 。GET
    ? 。POST
    ? 。PUT
    ? 。DELETE
    ? 。HEAD

    Play同時也支持以WebSocket的方式來調(diào)用服務(wù)器端的Action方法

    如果在路由文件中指定*作為HTTP方法,那么這個路由會匹配任何HTTP請求:

    * /clients/{id} Clients.show

    使用上述的路由配置,以下兩個HTTP請求都會被框架接受:

    GET /clients/1541 PUT /clients/1212

    4.1.2 URI表達(dá)式

    ? URI表達(dá)式定義了路由規(guī)則需要的請求路徑,請求路徑中允許存在動態(tài)內(nèi)容,但必須被聲明在{}中。

    /clients/all

    ? 以上的路由配置只能精確匹配到:

    /clients/all

    但是如果以包含動態(tài)部分配置路由規(guī)則:

    /clients/{id}

    ? 則可以分別匹配:

    /clients/12121

    ? 和

    /clients/toto

    如果某條路由配置的URI中需要包含多個動態(tài)部分,可以采用下例方法進(jìn)行配置:

    /clients/{id}/accounts/{accountId}

    ? 默認(rèn)情況下,動態(tài)部分的匹配策略采用的是正則表達(dá)式/[^/]+/。

    也可以為動態(tài)部分定義自己的正則表達(dá)式,以下是使用正則表達(dá)式的例子。

    路由規(guī)則只允許接受id為數(shù)字的值:

    /clients/{<[0-9]+>id}

    ? 路由規(guī)則確保id是長度為4到10字符的小寫單詞:

    /clients/{<[a-z]{4,10}>id}

    ? 正則表達(dá)式的使用非常靈活,還可以定義更多的路由規(guī)則,本節(jié)就不做贅述了。

    注意:
    動態(tài)部分指定后,控制器可以在HTTP參數(shù)map中獲取該值。

    默認(rèn)情況下,Play將URI尾部的斜線(“/”)作為重要的組成部分,因為有無“/”將會出現(xiàn)不同的結(jié)果。比如:

    GET /clients Clients.index

    ? 該路由規(guī)則會匹配/clients,而不是/clinets/(注意這里的區(qū)別),但可以通過在斜線后面增加問號來同時匹配兩個URI:

    GET /clients/? Clients.index

    注意:
    URI除了尾斜線不允許有其他可選的部分。

    4.1.3 定義Java調(diào)用

    ? 路由定義的最后部分為需要調(diào)用的Java方法:控制器中必須定義指定的Action方法,否則會提示找不到控制器方法的錯誤信息;

    必須聲明為public static void方法;控制器需作為play.mvc.Controller的子類定義在controllers包中。

    ? 如果控制器沒有在controllers包中定義,在配置路由規(guī)則時可以在其名稱之前增加Java包(比如admin.Dashboard.index)的說明。

    由于controllers包本身被Play默認(rèn)包含,所以用戶在配置路由時不需要顯式地指定。

    GET /admin admin.Dashboard.index

    4.1.4 404作為Action

    ? 可以直接使用404作為路由配置中的Action部分。如果這樣進(jìn)行配置,對應(yīng)的URL路徑就會被Play應(yīng)用所忽略。

    比如:

    # 忽略favicon請求 GET /favicon.ico 404

    4.1.5 指定靜態(tài)參數(shù)

    ? 在某些情況下,可能會需要基于不同的參數(shù)值定義特殊路由。以下是預(yù)先定義好的Action:

    public static void page(String id) {Page page = Page.findById(id);render(page); }

    ? 針對該Action,常規(guī)的路由配置為:

    GET /pages/{id} Application.page

    ? 現(xiàn)在給參數(shù)id=home的頁面指定一條特殊的URL,需要通過設(shè)置靜態(tài)參數(shù)來實現(xiàn):

    GET /home Application.page(id:'home') GET /pages/{id} Application.page

    ? 當(dāng)參數(shù)id=home時,兩條路由配置等價,但是由于前者具有較高的優(yōu)先級,

    ? 所以被作為默認(rèn)的URL來調(diào)用Application.page。

    4.1.6 變量和腳本

    ? 與模板中的使用方法類似,在routes文件中可以使用${…}作為變量表達(dá)式,使用%{…}作為腳本表達(dá)式,

    比如:

    %{ context = play.configuration.getProperty('context', '') }%# 主頁 GET ${context} Secure.login GET ${context}/ Secure.login

    ? 在路由文件中定義變量和腳本的典型例子是CRUD模塊的routes文件。

    該文件中使用crud.types標(biāo)簽對model類型進(jìn)行迭代,為每種類型生成控制器路由定義。

    以后文章會詳細(xì)介紹CRUD模塊的使用。

    #{crud.types} GET /? ${type.controllerClass.name.substring(12).replace('$','')}.index GET /${type.controllerName} ${type.controllerClass.name.substring(12).replace('$','')}.list GET /${type.controllerName}/new ${type.controllerClass.name.substring(12).replace('$','')}.blank GET /${type.controllerName}/{id} ${type.controllerClass.name.substring(12).replace('$','')}.show GET /${type.controllerName}/{id}/{field} ${type.controllerClass.name.substring(12).replace('$','')}.attachment GET /${type.controllerName}/{id}/edit ${type.controllerClass.name.substring(12).replace('$','')}.edit POST /${type.controllerName} ${type.controllerClass.name.substring(12).replace('$','')}.create POST /${type.controllerName}/{id} ${type.controllerClass.name.substring(12).replace('$','')}.save DELETE /${type.controllerName}/{id} ${type.controllerClass.name.substring(12).replace('$','')}.delete #{/crud.types}

    Play會按照聲明的順序,優(yōu)先選擇最先聲明的路由,比如:

    GET /clients/all Clients.listAll GET /clinets/{id} Clients.show

    ? 在上例的路由配置中,雖然請求/clients/all可以同時匹配這兩條路由配置,

    但按照聲明的優(yōu)先順序會被第一條路由攔截,并調(diào)用相應(yīng)的Clients.listAll方法。

    如果id參數(shù)需要匹配5個數(shù)字,在不使用重復(fù)規(guī)則的前提下,只能連續(xù)使用五個\d元字符,而使用重復(fù)規(guī)則后,規(guī)則的如下:

    GET /clinets/{<\d{5}>id} Clients.index

    ? 以下路由規(guī)則匹配2個大寫字母以及3-4個數(shù)字:

    GET /clinets/{<[A-Z]{2}[0-9]{3,4}>id} Clients.index

    4.1.7 staticDir:mapping

    ? Play的路由配置使用特殊的Action(staticDir)將存放靜態(tài)資源的public目錄開放。

    該目錄里包含的資源可以是圖片,Javascript,Stylesheet等,這些資源將直接響應(yīng)給客戶端,并不需要服務(wù)器做進(jìn)一步加工處理:

    GET /public/ staticDir:public
    當(dāng)客戶端請求/public/*路徑時,Play會從應(yīng)用的public文件夾中獲取相應(yīng)的靜態(tài)資源。這里的優(yōu)先級與標(biāo)準(zhǔn)路由配置一樣適用。

    4.1.8 staticFile:mapping

    ? 還可以直接將URL路徑映射為靜態(tài)文件:

    GET /home staticFile:/public/html/index.html

    ? 當(dāng)客戶端通過GET方法請求/home時,服務(wù)器將不做任何處理直接把/public/html目錄下面的index.html文件返回給客戶端。

    4.1.9 虛擬主機

    Play的路由器具有主機匹配功能,當(dāng)Action的變量需要從主機參數(shù)(指子域名,而不是子目錄)中獲取時,就顯得特別有用。

    比如SAAS應(yīng)用可以使用如下方式配置路由規(guī)則:

    GET {client}.mysoftware.com/ Application.index

    ? 根據(jù)以上配置,框架會自動獲取client的值作為請求的參數(shù):

    public static void index(String client) {... }

    ? 如果在模板中使用@@{…}標(biāo)簽,那么框架會根據(jù)指定的條件來選擇對應(yīng)的路由,這種方式在很多場合下都非常實用。比如,需要在產(chǎn)品中使用額外的服務(wù)器來提供靜態(tài)資源,則可以采用如下方式進(jìn)行路由配置:

    #{if play.Play.mode.isDev()}GET /public/ staticDir:public #{/} #{else}GET assets.myapp.com/ staticDir:public #{/}

    ? 對應(yīng)模板中的代碼如下:

    <img src="@@{'/public/images/logo.png'}">

    ? 當(dāng)應(yīng)用在DEV模式下運行時,靜態(tài)資源的URL為http://locahost:9000/public/images/logo.png;

    ? 如果運行在PROD模式下,URL為http://assets.myapp.com/images/logo.png。

    4.2 逆向生成URL

    Play路由器是按照J(rèn)ava調(diào)用生成URL的,所以可以將URI表達(dá)式都集中到同個配置文件中,使得重構(gòu)應(yīng)用變得更加便捷。

    比如,為conf/routes文件添加如下路由配置:

    GET /clients/{id} Clients.show

    ? 之后在Java代碼中,就可以調(diào)用Client.show來生成URL:

    map.put("id", 1541); String url = Router.reverse("Clients.show", map).url; // GET /clients/1541

    注意:
    URL的生成已經(jīng)集成到框架的大部分組件當(dāng)中,一般我們不需要直接調(diào)用Router.reverse方法。

    如果增加的參數(shù)不包含在URI表達(dá)式中,這些參數(shù)會被添加到查詢字符串中:

    map.put("id", 1541); map.put("display", "full"); String url = Router.reverse("Clients.show", map).url; // GET /clients/1541?display=full

    ? 同樣地,路由器會根據(jù)優(yōu)先順序匹配最適的URL。

    4.3 設(shè)置content type

    ? Play會根據(jù)request.format設(shè)定的值,選擇指定的media類型來響應(yīng)HTTP請求。

    該值通過文件擴展名來決定使用何種視圖模板進(jìn)行渲染,并且通過Play框架中的mime-types.properties文件進(jìn)行映射處理(映射關(guān)系詳見play\framework\src\play\libs\mime-types.properties文件),為media類型設(shè)定Content-type響應(yīng)。

    ? Play請求的默認(rèn)格式為html,因此index()控制器方法默認(rèn)的渲染模板文件為index.html。

    如果需要指定其他的格式,有以下四種方式:

    (1)可以在程序代碼調(diào)用render()方法之前進(jìn)行格式設(shè)置。比如將media類型設(shè)置為text/css,就可以使用CSS文件進(jìn)行渲染:

    public static void index() {request.format = "css"; render(); }

    (2)推薦一種更直接的做法,直接在routes文件中使用URL來指定格式。以下列路由配置為例:首先客戶端通過index.xml請求服務(wù)器,服務(wù)器端將響應(yīng)格式設(shè)置為xml,最后使用index.xml模版進(jìn)行渲染。

    GET /index.xml Application.index(format:'xml')

    ? 同樣地,我們也可以使用CSS進(jìn)行渲染:

    GET /stylesheets/dynamic_css css.SiteCSS(format:'css')

    (3)Play還可以直接從URL中獲取請求格式,動態(tài)指定渲染的模板類型。參考如下路由配置:

    GET /index.{format} Application.index

    ? 當(dāng)請求為/index.xml時,服務(wù)器會將返回格式設(shè)置為xml并使用相應(yīng)的XMl文件進(jìn)行渲染;

    ? 請求為/index.txt時,則會使用文本進(jìn)行渲染。

    (4)使用Play中的HTTP內(nèi)容協(xié)商進(jìn)行格式設(shè)置,詳見以后更新的內(nèi)容。

    4.4 HTTP內(nèi)容協(xié)商

    ? Play與其他REST架構(gòu)的框架一樣,直接使用HTTP方法,而不是試圖隱藏HTTP或者在上面構(gòu)建抽象層。

    內(nèi)容協(xié)商是HTTP的特性,它允許HTTP服務(wù)器根據(jù)客戶端的請求類型,實現(xiàn)同個URL提供不同的media類型響應(yīng)。

    客戶端可以在Accept header中設(shè)置media屬性,指定可接收的響應(yīng)類型。如果用戶需要XML響應(yīng),則進(jìn)行如下設(shè)置:

    Accept:application/xml

    ? 客戶端可以指定多種media類型,或使用cacth-all通配符(/)來指定任何media類型。

    Accept:application/xml,image/png,*/*

    ? 常規(guī)的Web瀏覽器總是在Accept header中包含了通配符的值,這樣瀏覽器便會接受任何media類型。

    Play將HTML作為默認(rèn)格式進(jìn)行渲染,因此在客戶端使用HTTP內(nèi)容協(xié)商就顯得特別有用:通過Ajax請求返回JSON格式,

    或是使文檔以PDF和EPUB形式顯示等

    4.3.1 在HTTP頭中設(shè)置content type

    ? 如果Accept header中包含了text/html,application/xhtml或者通配符 /,Play會選擇使用其默認(rèn)的請求格式(即HTML)。

    只有當(dāng)通配符的值被顯式指定時,Play才會選擇其默認(rèn)的請求格式。

    Play內(nèi)置了一些常規(guī)格式支持:html、txt、json、xml。

    下例代碼定義了控制器方法(Action)進(jìn)行數(shù)據(jù)渲染:

    public static void index() { final String name = "Peter Hilton"; final String organisation = "Lunatech Research"; final String url = "http://www.lunatech-research.com/"; render(name, organisation, url); }

    ? 如果在瀏覽器中訪問http://localhost:9000,Play默認(rèn)會使用index.html模板進(jìn)行渲染,因為瀏覽器發(fā)送了包含text/html的Accept header。

    通過將請求的格式設(shè)置為xml,可以使用index.xml模板響應(yīng)標(biāo)識為Accept: text/xml的請求:

    <?xml version="1.0"?> <contact> <name>${name}</name> <organisation>${organisation}</organisation> <url>${url}</url> </contact>

    ? 下表針對index()控制器方法給出了Play內(nèi)置的Accept header請求格式映射:

    Accept header包含了Play能夠映射成的所有格式(最后轉(zhuǎn)化為相應(yīng)的模板文件),如表3.1:

    ? (表3.1 Play內(nèi)置的Accept header請求格式映射)

    Accept headerFormatTemplate file namemapping
    nullnullindex.htmlnull格式請求提供默認(rèn)模版擴展
    image/pngnullindex.htmlmedia類型沒有映射為指定格式
    /, image/pnghtmlindex.html默認(rèn)將media類型映射為html格式
    text/htmlhtmlindex.html內(nèi)置映射
    application/xhtmlhtmlindex.html內(nèi)置映射
    text/xmlxmlindex.xml內(nèi)置映射
    application/xmlxmlindex.xml內(nèi)置映射
    text/plaintxtindex.txt內(nèi)置映射
    text/javascriptjsonindex.json內(nèi)置映射
    application/json, /jsonindex.json內(nèi)置映射, 忽略默認(rèn)media類型

    4.3.2 自定義格式

    ? 在Play中可以通過檢查HTTP請求頭,為應(yīng)用選擇相應(yīng)的media類型來實現(xiàn)自定義格式。

    比如使用@Before標(biāo)簽攔截該控制器下的所有Action,檢查請求的media類型是否為text/x-vcard:

    @Before static void setFormat() { if (request.headers.get("accept").value().equals("text/x-vcard")) { request.format = "vcf"; } }

    ? 如果檢查后發(fā)現(xiàn)請求頭中media類型為text/x-vcard時,將調(diào)用index.vcf模板渲染:

    BEGIN:VCARD VERSION:3.0 N:${name} FN:${name} ORG:${organisation} URL:${url} END:VCARD

    4.5 關(guān)于REST

    REST全稱為Representational State Transfer,即表述性狀態(tài)傳輸。

    它是一種為分布式超媒體系統(tǒng)(比如萬維網(wǎng))而設(shè)計的軟件架構(gòu)方式。REST定義了一些關(guān)鍵的規(guī)則:

    ? 。應(yīng)用的所有功能都被劃分為資源。
    ? 。每個資源都使用URI來唯一訪問。
    ? 。所有資源共享統(tǒng)一的接口用于客戶端與資源之間進(jìn)行狀態(tài)傳輸。

    如果應(yīng)用使用的是HTTP協(xié)議,那么這些接口是通過一系列可用的HTTP方法來定義的。

    HTTP協(xié)議往往通過以下方法來使用資源的狀態(tài):

    ? 。客戶端-服務(wù)器模式。
    ? 。無狀態(tài)模式。
    ? 。緩存模式。
    ? 。分層模式。

    如果應(yīng)用遵循了REST設(shè)計規(guī)則,那么該應(yīng)用就可以被稱為RESTful了。

    Play框架可以很容易地構(gòu)建RESTful應(yīng)用:

    ? 。Play的路由器通過解析URI和HTTP方法,將請求路由至Action方法,基于正則表達(dá)形式的URI為開發(fā)提供了更好的靈活性。
    ? 。協(xié)議是無狀態(tài)的,這意味著在兩次成功的請求之間不會把任何狀態(tài)保存在服務(wù)器中。
    ? 。Play將HTTP作為關(guān)鍵的特性,因此框架提供了對HTTP信息的完全訪問。

    5 play框架05

    ? play框架05–控制層

    5.1 概述

    Play的控制層位于應(yīng)用的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請求參數(shù)轉(zhuǎn)化為與之相匹配的Action方法參數(shù),這部分內(nèi)容會在后面的獲取HTTP參數(shù)小節(jié)進(jìn)行詳細(xì)講解。

    通常情況下,Action方法無需返回任何值,以調(diào)用結(jié)果方法來終止執(zhí)行。

    在上述例子中,render(…)方法就是用來渲染模板的結(jié)果方法。

    HTTP請求中往往包含各種參數(shù),這些參數(shù)的傳遞形式如下:

    • URI路徑:在路徑/clients/1541中,1541是URI的動態(tài)部分。

    • 查詢字符串:clients?id=1541。

    • 請求體:如果請求是來自HTML的表單提交(GET或者POST),

      那么請求體包含的是表單數(shù)據(jù)(采用x-www-urlform-encoded作為編碼格式)。

    針對以上幾種情況,Play會自動提取這些HTTP參數(shù)并將他們保存在Map<String,String>類型的變量中,以參數(shù)名作為Map的key。

    這些參數(shù)名分別來自于:

    • URI中動態(tài)部分的名稱(在routes文件中定義)。
    • 查詢字符串中“名稱/值”對中的名稱部分 。
    • 采用x-www-urlform-encoded編碼的表單數(shù)據(jù)的參數(shù)名。

    5.2 獲取HTTP參數(shù)

    5.2.1 使用Map參數(shù)

    HTTP請求中參數(shù)對象(params)在任何控制器中都是可訪問的(該實現(xiàn)在play.mvc.Controller超類中定義),

    它包含了當(dāng)前所有HTTP請求的參數(shù),并且可以通過get()方法得到,具體如下:

    public static void show(){String id=params.get("id");String[] names=params.getAll("name"); }

    這些參數(shù)也可以進(jìn)行類型轉(zhuǎn)換:

    public static void show(){Long id=params.get("id",Long.class); }

    本節(jié)將推薦一種更好的解決方案。Play框架提供了自動將Action聲明的參數(shù)與HTTP參數(shù)自動匹配的功能(只需要保持Action方法的參數(shù)名和HTTP參數(shù)名一致即可):

    /clients?id=1541

    Action方法可以在聲明中以id作為參數(shù),以此匹配HTTP中變量名為id的參數(shù):

    public static void show(String id){System.out.println(id); }

    當(dāng)然,也可以使用其他Java參數(shù)類型,而不僅僅是String。

    在下面的例子中框架會自動將參數(shù)轉(zhuǎn)換為正確的數(shù)據(jù)類型:

    public static void show(Long id){System.out.println(id); }

    如果參數(shù)含有多個值,那么可以定義數(shù)組參數(shù),具體如下:

    public static void show(Long[] id){for(Long anId:id){System.out.println(anId);} }

    參數(shù)甚至可以是List類型:

    public static void show(List<Long> id){for(Long anId:id){System.out.println(anId);} }

    注意:

    如果Action與HTTP之間的參數(shù)無法匹配,Play會將該參數(shù)設(shè)置為默認(rèn)值(通常情況下對象類型為null,原始數(shù)據(jù)類型為0)。

    如果參數(shù)可以匹配但不能正確進(jìn)行數(shù)據(jù)轉(zhuǎn)換,那么Play會先生成錯誤并添加到驗證器的error對象集合中,然后將參數(shù)設(shè)置為默認(rèn)值。

    5.2.2 高級HTTP綁定

    5.2.2.1 簡單類型

    Play可以實現(xiàn)所有Java原生的簡單數(shù)據(jù)類型的自動轉(zhuǎn)換,

    主要包括:int,long,boolean,char,byte,float,double,Integer,Long,Boolean,Char,String,Float,Double。

    日期類型

    如果HTTP參數(shù)字符串符合以下幾種數(shù)據(jù)格式,框架能夠自動將其轉(zhuǎn)換為日期類型:

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

    也可以根據(jù)不同地區(qū)的語言習(xí)慣對日期的格式做進(jìn)一步的優(yōu)化,具體如下:

    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。

    語言值可以通過逗號隔開,且需要與參數(shù)的個數(shù)相匹配。

    如果沒有使用@As注解來指定,Play會采用框架默認(rèn)的日期格式。為了使默認(rèn)的日期格式能夠正常工作,

    按照以下方式編輯application.conf文件:

    date.format=yyyy-MM-dd

    在application.conf文件中設(shè)置默認(rèn)的日期格式之后,就可以通過${date.format()}方法對模板中的日期進(jìn)行格式化操作了。

    5.2.2.2 日歷類型

    日歷類型和日期類型非常相像,當(dāng)然Play會根據(jù)本地化選擇默認(rèn)的日歷類型。

    讀者也可以通過@Bind注解來使用自定義的日歷類型。

    5.2.2.3 文件類型

    在Play中處理文件上傳是件非常容易的事情,首先通過multipart/form-data編碼的請求將文件發(fā)送到服務(wù)器,

    然后使用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); }

    新創(chuàng)建文件的名稱與原始文件一致,保存在應(yīng)用的臨時文件下(Application_name/tmp)。

    在實際開發(fā)中,需要將其拷貝到安全的目錄,否則在請求結(jié)束后會丟失。

    5.2.2.4 數(shù)組和集合類型

    所有Java支持的數(shù)據(jù)類型都可以通過數(shù)組或者集合的形式來獲取。

    數(shù)組形式:

    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) {... }

    例如下面的查詢字符串會轉(zhuǎn)化為帶有兩個元素的map類型,

    第一個元素key值為name,value為John;

    第二個元素key值為phone,value為111-1111, 222-2222。:

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

    5.2.2.5 POJO對象綁定

    Play使用同名約束規(guī)則(即HTTP參數(shù)名必須與模型類中的屬性名一致),自動綁定模型類:

    public static void create(Client client){client.save();show(client); }

    以下的查詢字符串可以通過上例的Action創(chuàng)建client:

    ?client.name=Zenexity&client.email=contact@zenexity.fr

    框架通過Action創(chuàng)建Client的實例,并將HTTP參數(shù)解析為該實例的屬性。如果出現(xiàn)參數(shù)無法解析或者類型不匹配的情況,會自動忽略。

    參數(shù)綁定是遞歸執(zhí)行的,這意味著可以深入到關(guān)聯(lián)對象:

    ?client.name=Zenexity &client.address.street=64+rue+taitbout &client.address.zip=75009 &client.address.country=France

    Play的參數(shù)綁定提供數(shù)組的支持,可以將對象id作為映射規(guī)則,更新一組模型對象。

    假設(shè)Client模型有一組聲明為List的customers屬性,那么更新該屬性需要使用如下查詢字符串:

    ?client.customers[0].id=123 &client.customers[1].id=456 &client.customers[2].id=789

    5.2.3 JPA對象綁定

    通過HTTP參數(shù)還可以實現(xiàn)JPA對象的自動綁定。Play會識別HTTP請求中提供的參數(shù)user.id,自動與數(shù)據(jù)庫中User實例的id進(jìn)行匹配。

    一旦匹配成功,HTTP請求中的其他User屬性參數(shù)可以直接更新到數(shù)據(jù)庫相應(yīng)的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

    5.2.4 自定義綁定

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

    ? @play.data.binding.As

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

    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注解允許對不需要綁定的屬性進(jìn)行標(biāo)記,以此來解決潛在的安全問題。

    比如:

    //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權(quán)限。

    play.data.binding.TypeBinder

    @As注解還提供完全自定義綁定的功能。自定義綁定必須是TypeBinder類的實現(xiàn):

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

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

    5.3 結(jié)果返回

    Action方法需要對客戶端作出HTTP響應(yīng),最簡單的方法就是發(fā)送結(jié)果對象。當(dāng)對象發(fā)送后,常規(guī)的執(zhí)行流程就會中斷。

    以下面這段代碼為例,最后一句System.out.println的輸出不會被執(zhí)行:

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

    render(…)方法向模板發(fā)送client對象,之后的其他語句將不會執(zhí)行,所以在控制臺中,

    并不會打印出“This message will never be displayed !”。

    5.3.1 返回文本內(nèi)容

    renderText(…)方法直接將文本內(nèi)容寫到底層HTTP響應(yīng)中:

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

    也可以通過Java標(biāo)準(zhǔn)的格式化語法對輸出的文本進(jìn)行處理:

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

    5.3.2 返回JSON字符串

    越來越多的應(yīng)用使用JSON作為數(shù)據(jù)格式進(jìn)行交互,Play對此進(jìn)行了很好的封裝,

    只需要使用renderJSON(…)方法就可以輕松地返回JSON字符串。

    在使用renderJSON(…)方法時,Play會自動將服務(wù)器返回的響應(yīng)的content type值設(shè)置為application/json,

    并且將renderJSON(…)方法中的參數(shù)以JSON格式返回。

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

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

    以上范例在使用renderJSON(…)方法時,傳入了拼接成JSON格式的字符串參數(shù)。

    Play框架會對其進(jìn)行自動設(shè)置,改變content type的值為application/json。

    當(dāng)然,renderJSON(…)方法的功能并不只有這些。因為大部分的應(yīng)用需求,都會要求服務(wù)端返回比較復(fù)雜的JSON格式,如果都采用字符串拼接的方式組成JSON內(nèi)容,就太不人性化了。renderJSON(…)的輸入?yún)?shù)還可以是復(fù)雜的對象,如果采用這種方式使用renderJSON(…)方法,Play在執(zhí)行renderJSON(…)時,底層會先調(diào)用GsonBuilder將對象參數(shù)進(jìn)行序列化,之后再將復(fù)雜的對象以JSON的格式返回給請求。這樣開發(fā)者就可以完全透明地使用renderJSON(…)方法,不需要做其他的任何操作了,以下代碼范例將會展示renderJSON(…)的這個功能。

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

    5.3.3 返回XML字符串

    與使用renderJSON(…)方法返回JSON內(nèi)容類似,如果用戶希望以XML格式對內(nèi)容進(jìn)行渲染,

    可以在Controller控制器中直接使用renderXml(…)方法。

    使用renderXml(…)方法時,Play會自動將服務(wù)器返回的響應(yīng)的content type值設(shè)置為application/xml。

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

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

    如果希望將復(fù)雜的對象以XML格式進(jìn)行渲染,可以在使用renderXml(…)方法時輸入org.w3c.dom.Document格式的對象,

    或者直接輸入POJO對象。以POJO對象作為參數(shù)使用renderXml(…)方法時,Play會使用XStream將其進(jìn)行序列化操作。

    同樣的,這些序列化操作都不需要由開發(fā)者去做,全部交給Play就行,開發(fā)者需要做的就是按照規(guī)范簡單地調(diào)用renderXml(…)方法即可。

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

    5.3.4 返回二進(jìn)制內(nèi)容

    Play為開發(fā)者提供了renderBinary(…)方法,可以非常方便的返回二進(jìn)制數(shù)據(jù)(如存儲在服務(wù)器里的文件、圖片等)給客戶端。

    以下代碼范例將會展示如何使用renderBinary(…)方法進(jìn)行二進(jìn)制圖片的渲染。

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

    首先,開發(fā)者需要建立用于持久化的域模型User,該User模型具有play.db.jpa.Blob類型的屬性photo。

    play.db.jpa.Blob是經(jīng)過Play封裝的特有的屬性類型,可以很方便的處理二進(jìn)制數(shù)據(jù)。之后,在Controller控制器中使用時,

    需要調(diào)用域模型的findById(…)方法加載持久化的數(shù)據(jù),并將圖片以二進(jìn)制數(shù)據(jù)流InputStream的形式進(jìn)行渲染。

    5.3.5 下載附件功能

    如果開發(fā)者希望將存儲在服務(wù)器端的文件,采用下載的形式渲染給客戶端用戶,需要對HTTP的header進(jìn)行設(shè)置。

    通常的做法是通知Web瀏覽器將二進(jìn)制響應(yīng)數(shù)據(jù)以附件的形式,下載至用戶的本地電腦上。

    在Play中完成這個功能非常簡單,只需要在使用renderBinary(…)方法時多傳入一個文件名的參數(shù)即可。

    這樣做會觸發(fā)renderBinary(…)的額外功能,提供文件名并設(shè)置響應(yīng)頭的Content-Disposition屬性。

    之后二進(jìn)制文件(包括圖片)將會以附件下載的形式,渲染給用戶。

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

    5.3.6 執(zhí)行模板

    如果需要響應(yīng)的內(nèi)容比較復(fù)雜,那么就應(yīng)該使用模板來進(jìn)行處理:

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

    模板的名稱遵從Play的約束規(guī)則,默認(rèn)的模板路徑采用控制器和Action的名稱相結(jié)合的方式來定義,

    比如在上述例子中,模板對應(yīng)的路徑為:app/views/Clients/index.html。

    5.2.7 為模板作用域添加數(shù)據(jù)

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

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

    在模板執(zhí)行過程當(dāng)中,client變量可以被使用:

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

    5.3.8 更簡單方式

    這里介紹一種更簡單的方式向模板傳遞數(shù)據(jù)。

    直接使用render(…)方法注入模板數(shù)據(jù):

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

    以該方式進(jìn)行數(shù)據(jù)傳遞,模板中可訪問的變量與Java本地變量的名稱(也就是render()方法中的參數(shù)名)一致。

    當(dāng)然也可以同時傳遞多個參數(shù):

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

    注意:

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

    5.3.9 指定其他模板進(jìn)行渲染

    如果讀者不希望使用默認(rèn)的模板進(jìn)行渲染,那么可以在renderTemplate(…)方法的第一個參數(shù)中指定其他自定義的模板路徑,

    例如:

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

    5.3.10 重定向URL

    redirect(…)方法產(chǎn)生HTTP重定向響應(yīng),可以將請求轉(zhuǎn)發(fā)到其他URL:

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

    5.3.11 自定義Web編碼

    Play推薦開發(fā)者使用UTF-8作為應(yīng)用開發(fā)的編碼格式,如果不進(jìn)行任何設(shè)置,Play框架默認(rèn)使用的也就是UTF-8格式。

    但是具體情況并不總是這么理想,有些特殊的需求可能要求某些響應(yīng)(response)的格式為ISO-8859-1,

    或者要求整個應(yīng)用都必須保持ISO-8859-1編碼。

    為當(dāng)前響應(yīng)設(shè)置編碼格式

    如果需要改變某一個響應(yīng)(response)的編碼格式,可以直接在Controller控制器中進(jìn)行修改,

    具體做法如下所示:

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

    ? 當(dāng)開發(fā)表單提交功能時,如果開發(fā)者希望某一表單提交的內(nèi)容采用非框架默認(rèn)使用的編碼(即Play框架采用默認(rèn)的編碼格式UTF-8,

    而該form表單提交的內(nèi)容希望采用ISO-8859-1編碼格式),Play的做法有一些特殊。在書寫form表單的HTML代碼時,

    需要對采用何種編碼格式進(jìn)行兩次標(biāo)識。首先需要在標(biāo)簽中添加accept-charset屬性(如:accept-charset=“ISO-8859-1”),

    accept-charset屬性會通知瀏覽器當(dāng)form表單提交的時候,采用何種編碼格式;

    其次,需要在form表單中添加hidden隱藏域,name屬性規(guī)定為“charset”,value屬性為具體需要的編碼格式,

    這樣做的目的是當(dāng)form提交的時候,可以通知服務(wù)端的Play采用何種編碼方式,

    具體范例如下:

    <form action="@{application.index}" method="POST" accept-charset="ISO-8859-1"><input type="hidden" name="_charset_" value="ISO-8859-1"> </form>

    定義全局編碼格式

    通常情況下,整個應(yīng)用應(yīng)該保持統(tǒng)一的編碼格式。

    如果開發(fā)者需要設(shè)置應(yīng)用全局的編碼格式,可以在application.conf配置文件中修改application.web_encoding屬性,配置相應(yīng)的編碼。

    5.4 Action鏈

    Play中的Action鏈與Servlet API中的forward不盡相同。Play的每次HTTP請求只能調(diào)用一個Action,

    如果需要調(diào)用其他的Action,那么必須將瀏覽器重定向到相應(yīng)的URL。

    在這種情況下,瀏覽器的URL始終與正在執(zhí)行的Action保持對應(yīng)關(guān)系,使得后退、前進(jìn)、刷新操作更加清晰。

    調(diào)用控制器中其他Action方法也可以實現(xiàn)重定向,框架會攔截該調(diào)用并生成正確的HTTP重定向。

    具體實現(xiàn)如下:

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

    相應(yīng)的路由規(guī)則定義如下:

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

    按照定義,Action鏈的生命周期為:

    • 瀏覽器向/clients發(fā)送POST請求;

    • 路由器調(diào)用Clients控制器中的create方法;

    • create方法直接訪問show方法;

    • Java調(diào)用被攔截,路由器逆向生成帶有id參數(shù)的URL來調(diào)用Clients.show;

    • HTTP響應(yīng)重定向為:/clients/3132;

    • 瀏覽器地址欄中URL展現(xiàn)為:/clients/3132;

    5.5 攔截器

    控制器中可以定義攔截方法(也可稱之為攔截器),為控制器及其子類的所有Action提供服務(wù)。

    當(dāng)所有的Action都需要進(jìn)行通用的處理時,該功能就顯得非常有用:比如驗證用戶的合法性,加載請求范圍內(nèi)的信息等。

    讀者在使用時需要注意的是,這些攔截器方法不能定義為public,但必須是static,并通過有效的攔截標(biāo)記進(jìn)行注解。

    5.5.1 @Before

    使用@Before注解的方法會在每個Action調(diào)用之前執(zhí)行。如創(chuàng)建具有用戶合法性檢查的攔截器:

    public class Admin extends Application {@Beforestatic void checkAuthentification() {if(session.get("user") == null) login();}public static void index() {List<User> users = User.findAll();render(users);}...}

    如果不希望@Before注解攔截所有的Action方法,那么可以使用unless參數(shù)列出需要排除的方法:

    public class Admin extends Application {@Before(unless="login")static void checkAuthentification() {if(session.get("user") == null) login();}public static void index() {List<User> users = User.findAll();render(users);}...}

    或者直接使用only參數(shù)把需要攔截的方法列舉出來:

    public class Admin extends Application {@Before(only={"login","logout"})static void doSomething() { ...}... }

    unless和only參數(shù)對@After,@Before以及@Finally注解都適用。

    5.5.2 @After

    使用@After注解的方法會在每個Action調(diào)用之后執(zhí)行:

    public class Admin extends Application {@Afterstatic void log() {Logger.info("Action executed ...");}public static void index() {List<User> users = User.findAll();render(users);}...}

    5.5.3 @Catch

    如果有Action方法拋出了異常,那么使用@Catch注解的方法就會執(zhí)行,且拋出的異常會以參數(shù)的形式傳遞到@Catch注解的方法中。

    具體實現(xiàn)如下:

    public class Admin extends Application {@Catch(IllegalStateException.class)public static void logIllegalState(Throwable throwable) {Logger.error("Illegal state %s…", throwable);}public static void index() {List<User> users = User.findAll();if (users.size() == 0) {throw new IllegalStateException("Invalid database - 0 users");}render(users);} }

    使用@Catch注解和普通的Java異常處理程序一樣,捕獲父類往往可以獲得更多的異常類型。

    如果擁有多個需要捕獲的方法,可以通過指定優(yōu)先級來確定他們的執(zhí)行順序。具體實現(xiàn)如下:

    public class Admin extends Application {@Catch(value = Throwable.class, priority = 1)public static void logThrowable(Throwable throwable) {// Custom error logging…Logger.error("EXCEPTION %s", throwable);}@Catch(value = IllegalStateException.class, priority = 2)public static void logIllegalState(Throwable throwable) {Logger.error("Illegal state %s…", throwable);}public static void index() {List<User> users = User.findAll();if(users.size() == 0) {throw new IllegalStateException("Invalid database - 0 users");}render(users);} }

    5.5.4 @Finally

    @Finally注解的方法總是在每個Action調(diào)用之后執(zhí)行(無論Action是否成功執(zhí)行):

    public class Admin extends Application {@Finallystatic void log() {Logger.info("Response contains : " + response.out);}public static void index() {List<User> users = User.findAll();render(users);}...}

    如果@Finally注解的方法中包含的參數(shù)是可拋出的異常,其方法中的內(nèi)容還是可以繼續(xù)執(zhí)行的,具體如下:

    public class Admin extends Application {@Finallystatic void log(Throwable e) {if( e == null ){Logger.info("action call was successful");} else{Logger.info("action call failed", e);}}public static void index() {List<User> users = User.findAll();render(users);}... }

    5.5.5 使用@with注解增加更多攔截器

    如果某個控制器是其他一些類的父類,那么該控制器中定義的所有攔截器會影響到所有子類。

    由于Java不允許多重繼承,對單純通過繼承來使用攔截器造成了一定的局限性。

    Play可以通過@With注解,調(diào)用其他控制器中已經(jīng)定義好的攔截方法,從而突破這一局限。

    比如創(chuàng)建Secure控制器,定義checkAuthenticated()攔截方法驗證用戶合法性:

    public class Secure extends Controller {@Beforestatic void checkAuthenticated() {if(!session.containsKey("user")) {unAuthorized();}} }

    在其他的控制器中,可以通過@With(Secure.class)注解將其包含進(jìn)來:

    @With(Secure.class) public class Admin extends Application {... }

    5.6 Session和Flash作用域

    在Play開發(fā)中,如果需要在HTTP請求之間保存數(shù)據(jù),可以將數(shù)據(jù)保存在Session或者Flash內(nèi)。

    保存在Session中的數(shù)據(jù)在整個用戶會話中都是有效的,而保存在Flash的數(shù)據(jù)只對下一次請求有效。

    特別需要注意的是,Session和Flash作用域中的數(shù)據(jù)都是采用Cookie機制添加到隨后的HTTP響應(yīng)中的(并沒有存儲在服務(wù)器上的),

    所以數(shù)據(jù)大小非常有限(不能超過4K),而且只能存儲字符串類型的數(shù)據(jù)。

    由于Cookie是使用密鑰簽名過的,所以客戶端不能輕易修改Cookie的數(shù)據(jù)(否則會失效)。

    不要將Play的Session當(dāng)作緩存來使用,如果需要在特定的會話中緩存一些數(shù)據(jù),那么可以使用Play內(nèi)置的緩存機制,

    并將session.getId()作為緩存的key進(jìn)行儲存。

    public static void index() {List messages = Cache.get(session.getId() + "-messages", List.class);if(messages == null) { // 處理緩存失效messages = Message.findByUser(session.get("user"));Cache.set(session.getId() + "-messages", messages, "30mn");}render(messages); }

    Session在用戶關(guān)閉瀏覽器后就會失效,除非修改配置文件中的application.session.maxAge屬性。

    設(shè)置方法如下:

    application.session.maxAge=7d # Remember for one week.

    使用Play內(nèi)置的Cache緩存時需要注意,Cache與傳統(tǒng)Servlet的HTTP Session對象是不同的。

    框架無法保證這些緩存對象會一直存在,所以在業(yè)務(wù)代碼中必須處理緩存失效的問題,以便保持應(yīng)用完全無狀態(tài)化。

    6 play框架06

    ? play框架06–模板語法、模板繼承

    Play具有高效的模板體系,采用Groovy作為其表達(dá)式語言,允許動態(tài)生成HTML、XML、JSON或者任何基于文本格式的文檔,

    并且具有創(chuàng)建可重用標(biāo)簽(tag)的功能。

    模板儲存在Play應(yīng)用的app/views目錄下。

    6.1 模板語法

    與其他的語言一樣,Play的模板也具有嚴(yán)格定義的語法。模板語法被劃分為多種元素,用于完成不同類型的任務(wù)。

    Play模板的本質(zhì)是普通的文本文件,其中帶有占位符的部分可以生成動態(tài)內(nèi)容。

    模板的動態(tài)部分采用Groovy語言編寫,其語法與Java非常類似。

    框架可以將需要渲染的結(jié)果追加至HTTP響應(yīng)的數(shù)據(jù)部分,并發(fā)送至模板。

    所有的動態(tài)部分將會在模板的執(zhí)行期間被解析。

    ? (表1 模板語法)

    元素描述語法
    表達(dá)式用于輸出表達(dá)式的值。如 ${note.title} 的作用是將域?qū)ο髇ote的屬性title的值輸出。${…}
    標(biāo)簽用于調(diào)用Play框架內(nèi)置的或是開發(fā)人員自定義的標(biāo)簽。如#{get ‘title’ /}:獲取變量 title 的值,該值僅在模板頁面中有效。#{…}
    引用用于生成調(diào)用控制器中Action方法的URL,在頁面鏈接中使用的最為頻繁。@{…} 和 @@{…} 的區(qū)別在于生成的URL分別是相對路徑還是絕對路徑。如: 1.首頁:生成指向首頁的鏈接。 2.@{’/public/stylesheets/main.css’}:引入CSS靜態(tài)資源文件。@{…} 和@@{…}
    國際化用于顯示經(jīng)過國際化處理后的消息內(nèi)容。&{…}
    注釋用于在模板中添加注釋。如:{ 這是注釋 }。{…}
    腳本用于添加復(fù)雜的 Groovy 腳本,可以聲明變量和執(zhí)行業(yè)務(wù)邏輯。%{…}%

    6.1.1 表達(dá)式(expression)😒{…}

    構(gòu)建動態(tài)部分最簡單的方法就是聲明表達(dá)式。表達(dá)式需要以 ${ 開頭, 并以 } 結(jié)尾,作為占位符使用。

    具體例子如下:

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

    以上是輸出客戶姓名的表達(dá)式例子。在該例子中,將客戶類定義為Client。

    經(jīng)過控制器中Action的業(yè)務(wù)邏輯執(zhí)行,首先需要向模板注入對象client,之后就可以在模板中使用${…}表達(dá)式語法輸出client對象的name屬性。

    如果不能確定向模板注入的client對象是否為null,可以使用如下Groovy快捷語法:

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

    此時,只有client不為null的情況下,才進(jìn)行client.name的輸出。

    6.1.2 標(biāo)簽(tag): #{tagName /}

    標(biāo)簽是能夠附帶參數(shù)調(diào)用的模板片段,如果標(biāo)簽只有一個參數(shù),按照約定,參數(shù)的名稱為arg,并且該參數(shù)名是可以省略的。

    例如,可以使用#{script}標(biāo)簽加載JavaScript文件:

    #{script 'jquery.js' /}

    Play模板中的標(biāo)簽必須是閉合的,可以通過兩種方式閉合標(biāo)簽。采用直接閉合的形式:

    #{script 'jquery.js'/}

    或者起始標(biāo)簽和結(jié)束標(biāo)簽成對地使用:

    #{script 'jquery.js'}#{/script}

    #{script}是Play模板的內(nèi)置標(biāo)簽,由框架實現(xiàn),可以直接使用。后面會進(jìn)一步介紹如何自定義標(biāo)簽來滿足開發(fā)中一些特定的需求。

    #{list}標(biāo)簽可以對集合類進(jìn)行迭代操作,使用時需要注意,必須帶有兩個參數(shù)(items以及as):

    <h1>Client ${client.name}</h1> <ul>#{list items:client.accounts, as:'account' }<li>${account}</li>#{/list} </ul>

    上例中使用#{list}標(biāo)簽對client.accounts集合進(jìn)行迭代,并將集合中的每一條數(shù)據(jù)作為account在頁面中輸出。

    在應(yīng)用中,模板引擎默認(rèn)對所有的動態(tài)表達(dá)式進(jìn)行轉(zhuǎn)義,以此來避免XSS的安全問題。

    如果模板中變量${title}的內(nèi)容為

    Title

    ,在頁面輸出時會自動進(jìn)行轉(zhuǎn)義:

    ${title} --> &lt;h1&gt;Title&lt;/h1&gt;

    也可以通過調(diào)用擴展方法raw(),以非轉(zhuǎn)義的形式在頁面中輸出,具體使用方法如下:

    ${title.raw()} --> <h1>Title</h1>

    如果需要顯示大量的非轉(zhuǎn)義HTML內(nèi)容,可以使用#{verbatim /}標(biāo)簽:

    #{verbatim}${title} --> <h1>Title</h1> #{/verbatim}

    6.1.3 引用(action)😡{…}或者@@{…}

    在前面的章節(jié)已經(jīng)有過一些介紹,Play通過路由器可以(逆向)生成URL,匹配指定的路由。

    在模板中使用@{…}引用可以達(dá)到相同的目的:

    <h1>Client ${client.name}</h1> <p><a href="@{Clients.showAccounts(client.id)}">All accounts</a> </p> <hr /><a href="@{Clients.index()}">Back</a>

    該實例中,@{Clients.showAccounts(client.id)}調(diào)用了Clients控制器中的showAccounts Action方法,并傳遞了client.id參數(shù)。

    @@{…}引用的使用語法與@{…}相同,只不過生成的是絕對URL(尤其適用于郵箱)。

    6.1.4 國際化(messages):&{…}

    如果應(yīng)用需要進(jìn)行國際化操作,那么可以在模板中使用&{…}顯示國際化信息。

    需要進(jìn)行國際化的應(yīng)用首先需要在conf/messages文件中進(jìn)行國際化定義:

    clientName=The client name is %s

    之后在模板中就可以通過&{…}顯示該國際化信息了:

    <h1>&{'clientName',client.name}</h1>

    6.1.5 注釋(comment):

    *{…}*使用*{…}*標(biāo)記的內(nèi)容會被模板引擎忽略,起到注釋作用:*{**** Display the user name ****}* <div class="name">${user.name} </div>

    6.1.6 腳本(script): %{…}%

    腳本是更加復(fù)雜的表達(dá)式集合,能夠聲明一些變量或者定義一些語句。

    Play的模板中使用%{…}%插入腳本:

    %{fullName = client.name.toUpperCase()+' '+client.forname; }%<h1>Client ${fullName}</h1>

    也可以直接使用out內(nèi)置對象輸出動態(tài)內(nèi)容:

    %{fullName = client.name.toUpperCase()+' '+client.forname;out.print('<h1>'+fullName+'</h1>'); }%

    在模板中還可以使用腳本編寫結(jié)構(gòu)化語句,執(zhí)行一些邏輯操作,比如迭代:

    <h1>Client ${client.name}</h1> <ul> %{for(account in client.accounts) { }%<li>${account}</li> %{} }% </ul>

    使用模板時切記:模板不適合處理復(fù)雜的業(yè)務(wù)邏輯,所以在模板中請盡量使用標(biāo)簽,或者直接將處理交給控制器或模型對象。

    6.2 模板繼承

    Play提供繼承機制為多模板之間進(jìn)行頁面布局或設(shè)計提供服務(wù)。

    #{extends /} 和 #{doLayout /}可以使頁面的共享和重用變得更加方便簡潔,

    同時也能夠幫助開發(fā)人員實現(xiàn)頁面中動態(tài)內(nèi)容和靜態(tài)外觀裝飾的分離。

    在模板與裝飾之間可以使用#{get}和#{set}標(biāo)簽進(jìn)行參數(shù)傳遞。

    將simpledesign.html定義為父模板,也就是裝飾模板。

    可以將頁面中重用的靜態(tài)內(nèi)容定義在裝飾模板中,如導(dǎo)航條、頁面頂端的圖片banner、頁面底端的footer說明等。

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head><title>#{get 'title' /}</title><link rel="stylesheet" type="text/css" href="@{'/public/stylesheets/main.css'}" /> </head><body><h1>#{get 'title' /}</h1>#{doLayout /}<div class="footer">Built with the play! framework</div> </body> </html>

    在simpledesign.html中將、

    、 中的內(nèi)容定義為公用元素,

    所有繼承于該模板的頁面都會包含這些內(nèi)容。其中#{doLayout /}標(biāo)簽起到占位的作用,包含其子模板的頁面內(nèi)容。

    其他所有繼承于simpledesign.html模板的頁面內(nèi)容都將顯示在#{doLayout /}所占的頁面區(qū)塊。

    其他頁面使用#{extends}標(biāo)簽可以非常簡單地植入該裝飾模板,

    具體使用方法如下:

    #{extends 'simpledesign.html' /}#{set title:'A decorated page' /} This content will be decorated.

    該子模板使用#{extends ‘simpledesign.html’ /}標(biāo)簽來繼承simpledesign.html,

    使用#{set title:‘A decorated page’ /}標(biāo)簽傳遞頁面的title變量,

    最后在頁面中輸出This content will be decorated。

    7 play框架07

    ? play框架07–域模型

    7.1 屬性模擬

    查看Play提供的示例應(yīng)用,模型類里面會頻繁地使用聲明為public的變量。即使是經(jīng)驗尚淺的Java開發(fā)者,也懂得慎用public類型的變量。

    在Java開發(fā)中(當(dāng)然還有其他的面向?qū)ο笳Z言),實踐經(jīng)驗是這樣告訴我們的:將所有的成員變量聲明為私有,只提供獲取與修改的方法。

    這樣做的目的在于增強程序的封裝性,而“封裝”在面向?qū)ο笤O(shè)計中恰恰是非常關(guān)鍵的概念。

    Java沒有真正的內(nèi)置屬性定義機制,而是使用Java Bean來進(jìn)行約束:Java對象的屬性通過一對getXxx/setXxx的方法來修改,

    如果對象是只讀的那么只需要提供getXxx方法。

    在過去的開發(fā)中我們一直這樣做,但是編碼過程就顯得有些乏味了。

    每個屬性必須聲明為private,同時還有相應(yīng)的getXxx/setXxx方法,

    而且大多數(shù)情況下,getXxx和setXxx方法的實現(xiàn)都是類似的。

    private String name; public String getName() {return name; } public void setName(String value) {name = value; }

    Play框架的模型部分會自動生成getXxx/setXxx方法,保持代碼的簡潔。

    也就是說,在Play中開發(fā)者可以直接把屬性變量聲明為public,運行時Play會自動生成相應(yīng)的getXxx/setXxx方法(在這里我們將聲明為public的字段都視為屬性)。

    public class Product {public String name;public Integer price; }

    上述代碼被框架載入后就會轉(zhuǎn)換成如下形式:

    public class Product {public String name;public Integer price;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getPrice() {return price;}public void setPrice(Integer price) {this.price = price;} }

    因為變量被聲明為public,可以使用如下方式對屬性進(jìn)行操作:

    product.name = "My product"; product.price = 58;

    程序在加載時會自動地轉(zhuǎn)換成:

    product.setName("My product"); product.setPrice(58);

    注意:

    因為這些getXxx/setXxx方法是在運行時動態(tài)生成的,所以不能直接調(diào)用。

    如果在編碼階段使用他們,編譯器會因為找不到該方法而報錯誤。

    當(dāng)然也可以自己定義相應(yīng)的getXxx/setXxx方法,Play會優(yōu)先選擇手動編寫的方法。

    如果需要保護(hù)Product類的price屬性就可以定義setPrice()方法:

    public class Product {public String name;public Integer price;public void setPrice(Integer price) {if (price < 0) {throw new IllegalArgumentException("Price can’t be negative!");}this.price = price;} }

    如果為Product類的price屬性賦負(fù)值就會拋出異常:

    product.price = -10: // Oops! IllegalArgumentException

    Play總會優(yōu)先使用已經(jīng)定義的getXxx/setXxx方法:

    @Entity public class Data extends Model {@Requiredpublic String value;public Integer anotherValue;public Integer getAnotherValue() {if(anotherValue == null) {return 0;}return anotherValue;}public void setAnotherValue(Integer value) {if(value == null) {this.anotherValue = null;} else {this.anotherValue = value * 2;}} public String toString() {return value + " - " + anotherValue;} }

    補充:

    @Entity注解的作用是通知Play自動開啟JPA實體管理器,@Required是對該屬性的約束。

    該類繼承于play.db.jpa.Model,Model提供了非常簡單的對象處理方式,在后面章節(jié)會做詳細(xì)介紹。

    針對以上例子可以進(jìn)行如下測試斷言:

    Data data = new Data(); data.anotherValue = null; assert data.anotherValue == 0; data.anotherValue = 4 assert data.anotherValue == 8;

    以上的斷言都會執(zhí)行通過,而且因為這種改進(jìn)的類遵從JavaBean規(guī)范,可以滿足開發(fā)中的更復(fù)雜需求。

    7.2 數(shù)據(jù)庫配置

    通常情況下,開發(fā)者需要將模型對象持久化。最常用的方法是把這些數(shù)據(jù)保存到數(shù)據(jù)庫中。

    在Play應(yīng)用的開發(fā)過程中,開發(fā)者可以迅速配置嵌入式內(nèi)存數(shù)據(jù)庫或者直接將數(shù)據(jù)保存到文件系統(tǒng)中。

    開啟內(nèi)存數(shù)據(jù)庫H2,只需要在conf/application.conf文件中進(jìn)行如下配置:

    db=mem

    補充:

    H2是開放源代碼的Java數(shù)據(jù)庫,其具有標(biāo)準(zhǔn)的SQL語法和Java接口,可以自由使用和分發(fā),且非常簡潔和快速。

    將數(shù)據(jù)保存在內(nèi)存中相比從磁盤上訪問能夠極大地提高應(yīng)用的性能,

    但由于內(nèi)存容量的限制,內(nèi)存數(shù)據(jù)庫適用于開發(fā)階段,或者原型示例開發(fā)。

    如果需要將數(shù)據(jù)保存在文件系統(tǒng)中,則使用如下配置:

    db=fs

    如果需要連接到MySQL服務(wù)器,則使用如下配置:

    db=mysql:user:pwd@database_name

    Play框架集成了H2數(shù)據(jù)庫和MySQL數(shù)據(jù)庫的驅(qū)動程序,存放在$PLAY_HOME/framework/lib/目錄下。

    如果需要使用PostgreSQL,Oracle或者其他數(shù)據(jù)庫,需要在該目錄(或者應(yīng)用程序的lib/目錄)下添加相應(yīng)的數(shù)據(jù)庫驅(qū)動。

    Play可以連接任何JDBC兼容的數(shù)據(jù)庫,只需要將相應(yīng)的驅(qū)動類庫添加到/lib目錄中,并在conf/application.conf文件中定義JDBC配置:

    db.url=jdbc:mysql://localhost/test db.driver=com.mysql.jdbc.Driver db.user=root db.pass=123456

    還可以在conf/application.conf文件中用配置選項指定JPA方言:

    jpa.dialect=<dialect>

    補充:

    由于不同的數(shù)據(jù)庫產(chǎn)品支持不同的ANSI SQL標(biāo)準(zhǔn),所以Hibernate必須要使用“方言”才能與各種數(shù)據(jù)庫成功的進(jìn)行通信。

    在Play中,大多數(shù)情況下會自動根據(jù)配置信息識別特定數(shù)據(jù)庫方言,但是存在某些數(shù)據(jù)庫,Play無法判斷其使用的方言。

    這時就需要開發(fā)者顯式地在Play配置文件中指定。

    除了使用Hibernate外,在編碼時還可以直接從play.db.DB中獲得java.sql.Connection,然后使用標(biāo)準(zhǔn)SQL語句來執(zhí)行數(shù)據(jù)庫操作。

    Connection conn = DB.getConnection(); conn.createStatement().execute("select * from products");

    7.3 數(shù)據(jù)持久化

    Play的持久層框架采用的是Hibernate,使用Hibernate(通過JPA)自動地將Java對象持久化到數(shù)據(jù)庫。

    當(dāng)在任意的實體類上增加@javax.persistence.Entity注解后,Play會自動為其開啟JPA實體管理器。

    @Entity public class Product {public String name;public Integer price; }

    注意:

    Play應(yīng)用開發(fā)者一開始可能經(jīng)常會犯的錯誤是使用Hibernate的@Entity注解來取代JPA。

    這里請讀者注意,Play是直接調(diào)用JPA的API來使用Hibernate。

    也可以直接從play.db.jpa.JPA對象中得到實體管理器,通過實體管理器可以將Model持久化到數(shù)據(jù)庫或者執(zhí)行HQL語句,

    例如:

    EntityManager em = JPA.em(); em.persist(product); em.createQuery("from Product where price > 50").getResultList();

    Play為JPA的使用提供了非常好的支持,只需要繼承Play提供的play.db.jpa.Model類:

    @Entity public class Product extends Model {public String name;public Integer price; }

    接著就可以執(zhí)行Product實例中CRUD操作進(jìn)行對象持久化:

    Product.find("price > ?", 50).fetch(); Product product = Product.findById(2L); product.save(); product.delete();

    補充:ActiveRecord模式

    ActiveRecord也屬于ORM層,由Rails最早提出,遵循標(biāo)準(zhǔn)的ORM模型:表映射到記錄,記錄映射到對象,字段映射到對象屬性。

    配合遵循的命名和配置慣例,能夠很大程度的快速實現(xiàn)模型的操作,而且簡潔易懂。

    Play也提倡使用ActiveRecord模式進(jìn)行快速開發(fā),其主要思想是:

  • 每一個數(shù)據(jù)庫表對應(yīng)創(chuàng)建一個類,類的每一個對象實例對應(yīng)于數(shù)據(jù)庫中表的一行記錄;通常表的每個字段在類中都有相應(yīng)的Field;
  • ActiveRecord同時負(fù)責(zé)把自己持久化,在ActiveRecord中封裝了對數(shù)據(jù)庫的訪問,即CURD;
  • ActiveRecord是一種領(lǐng)域模型(Domain Model),封裝了部分業(yè)務(wù)邏輯。
  • 7.4 無狀態(tài)模型

    Play被設(shè)計成為“無共享”的架構(gòu),目的就是為了保持應(yīng)用的完全無狀態(tài)化。

    這樣做的好處在于可以讓一個應(yīng)用同一時刻在多個服務(wù)器節(jié)點上運行。

    Play為了保持模型無狀態(tài)化,需要避免一些常見的陷阱,最重要的就是不要因為多請求而將對象保存到Java堆中。

    Play應(yīng)用中多請求之間保存數(shù)據(jù)有以下幾種解決方案:

    1.如果數(shù)據(jù)很小而且非常簡單,那么可以將其存儲在Session或者Flash作用域,

    但是這些作用域最大只允許存放4K的內(nèi)容,并且存儲的數(shù)據(jù)只能為字符串類型。

    2.將數(shù)據(jù)保存到持久化存儲中(數(shù)據(jù)庫或者文件系統(tǒng))。

    比如用戶創(chuàng)建了需要跨越多個請求的對象,就可以按照以下步驟對其進(jìn)行操作:

    • 在第一次請求時初始化對象并將它保存到數(shù)據(jù)庫中。

    • 將創(chuàng)建的對象的id保存在Flash作用域中。

    • 在以后不停的請求鏈執(zhí)行過程中,使用id從數(shù)據(jù)庫中獲取對象,更新并重新保存它。

      3.將數(shù)據(jù)保存在瞬時存儲中(比如Cache):

    • 在第一次請求時初始化對象并將它保存在緩存中。

    • 將創(chuàng)建的對象的id保存在Flash作用域中。

    • 在請求鏈的執(zhí)行過程中,從Cache里獲取對象(通過保存在Flash作用域中的對象id),更新后并將它再次保存回Cache。

    • 當(dāng)請求鏈結(jié)束后,將對象進(jìn)行持久化操作(數(shù)據(jù)庫或者文件系統(tǒng))。

    根據(jù)具體應(yīng)用的需求,第三種解決方案使用緩存可以是一種非常好的選擇,也是Java Servlet Session的良好的替代方案。

    但緩存并不是可靠的數(shù)據(jù)存儲方式,因此如果選擇將對象保存到緩存中,就必須確保能夠?qū)⑺匦伦x取回來。

    8 play框架08

    ? play框架08–Job異步處理

    8.1 Job實現(xiàn)

    在Play中建立Job只需要繼承play.jobs.Job類:

    package jobs; import play.jobs.*; public class MyJob extends Job {public void doJob() {// 執(zhí)行一些業(yè)務(wù)邏輯} }

    如果希望創(chuàng)建具有返回值的Job,那么需要覆蓋doJobWithResult()方法:

    package jobs; import play.jobs.*; public class MyJob extends Job<String> {public String doJobWithResult() {// 執(zhí)行一些業(yè)務(wù)邏輯return result;} }

    上例自定義的Job覆蓋了doJobWithResult()方法,并且方法的返回類型為String,事實上Job可以返回任何類型的值。

    8.2 Bootstrap Job

    8.2.1 應(yīng)用啟動

    Bootstrap Job,顧名思義就是在應(yīng)用開始時運行的任務(wù),

    只需要添加@OnApplicationStart注解就可以把當(dāng)前Job設(shè)置為Bootstrap Job:

    import play.jobs.*;@OnApplicationStart public class Bootstrap extends Job {public void doJob() {if(Page.count() == 0) {new Page("root").save();Logger.info("A root page has been created.");}}}

    需要注意的是,Bootstrap Job不需要任何返回值。

    如果有多個帶有@OnApplicationStart注解的Bootstrap Job,那么默認(rèn)情況下這些Job會按照定義的先后順序執(zhí)行。

    當(dāng)所有的Bootstrap Job執(zhí)行完成之后,Web應(yīng)用就處于等待階段,等待處理那些即將到來的請求。

    如果希望Web應(yīng)用啟動后,能夠在執(zhí)行Bootstrap Job的同時,又能很快地處理到來的請求,

    可以為@OnApplicationStart注解添加async=true屬性:@OnApplicationStart(async=true)。

    這樣應(yīng)用程序開啟后,Bootstrap Job就會作為后臺程序異步執(zhí)行了。

    不僅如此,所有的異步Job(async=true)也會在Web應(yīng)用開啟之后同時運行。

    注意:

    Play具有兩種不同的工作模式:開發(fā)模式(DEV)和產(chǎn)品模式(PROD),因此Job的啟動時間也有略微差異。

    在DEV模式下,直到第一個HTTP請求到達(dá)時才會開啟應(yīng)用,且不會預(yù)先編譯Java文件。

    如果在該模式下更改Java源文件可以立即生效,刷新瀏覽器即可查看修改后的結(jié)果。

    此外,應(yīng)用還會在需要的時候自動重啟;而在PROD模式下,應(yīng)用會在服務(wù)器啟動時同步開啟,一旦應(yīng)用開啟就會自動編譯所有的Java文件,之后不再重載任何文件(包括模板文件和配置文件),所以如果有文件修改必須重啟應(yīng)用才能生效。

    所以DEV模式下Job會延遲啟動。

    8.2.2 應(yīng)用停止

    Web應(yīng)用停止或關(guān)閉的時候,常常也需要進(jìn)行一些額外的操作,如進(jìn)行數(shù)據(jù)的清理、日志的打印等。

    如果開發(fā)者需要這類任務(wù)調(diào)度操作,可以使用Play提供的@OnApplicationStop注解。

    import play.jobs.*;@OnApplicationStop public class Bootstrap extends Job {public void doJob() {Fixture.deleteAll();} }

    用法非常簡單,繼承Job類之后,重寫doJob()方法即可。

    8.3 Scheduled Job

    Scheduled Job是指可以被框架周期性執(zhí)行的任務(wù),可以使用@Every注解指定時間間隔控制Scheduled Job運行,

    例如:

    import play.jobs.*;@Every("1h") public class Bootstrap extends Job {public void doJob() {List<User> newUsers = User.find("newAccount = true").fetch();for(User user : newUsers) {Notifier.sayWelcome(user);}}}

    在實際開發(fā)中,@Every注解并不能夠完全滿足開發(fā)需求,比如有時候需要指定Scheduled Job在具體的某個時間點執(zhí)行。

    這時候可以使用@On注解指定時間點來執(zhí)行Job,例如:

    import play.jobs.*;/** Fire at 12pm (noon) every day **/ @On("0 0 12 * * ?") public class Bootstrap extends Job {public void doJob() {Logger.info("Maintenance job ...");...}}

    與Bootstrap Job一樣,Scheduled Job也是不需要任何返回值的,即使返回了也會丟失。

    補充:

    @On標(biāo)簽中使用的是Quartz庫的CRON表達(dá)式。CRON表達(dá)式是由7個子表達(dá)式組成的字符串,每個子表達(dá)式都描述了單獨的日程細(xì)節(jié)。

    這些子表達(dá)式用空格分隔,分別表示:

    Seconds 秒 Minutes 分鐘 Hours 小時 Day-of-Month 一個月中的某一天 Month 月 Day-of-Week 一周中的某一天 Year 年(可選) 具體CRON表達(dá)式的例子:"0 0 12 ? * WED",表示“每周三的中午12:00”。

    8.4 Job的直接調(diào)用

    Play的Job除了被框架自動調(diào)用外,也可以通過now()方法手動調(diào)用Job對象的實例,隨時觸發(fā)Job來執(zhí)行指定任務(wù)。

    使用now()方法調(diào)用Job后,任務(wù)會立即執(zhí)行:

    public static void encodeVideo(Long videoId) {new VideoEncoder(videoId).now();renderText("Encoding started"); }

    now()方法的返回值是Promise對象,通過該值可以在結(jié)束后獲得Job的執(zhí)行結(jié)果。

    9 play框架的請求處理流程

    ? Play框架的請求處理流程

    Play框架使用事件驅(qū)動模型,以提供在不可預(yù)知的使用環(huán)境下的靈活的處理能力。

    在一個web應(yīng)用中,事件主要指用戶向服務(wù)器發(fā)起一次HTTP請求。對于Play框架,此類事件定義在routes文件中,play根據(jù)routes文件的內(nèi)容以及用戶的請求,確定應(yīng)該調(diào)用哪些過程。Play框架使用了Netty服務(wù)器,該服務(wù)器使用管道(pipeline),提供了在高并發(fā)情況下的優(yōu)秀的異步處理能力。

    當(dāng)服務(wù)器接收到一個用戶請求的時候,將獲取一個管道,將請求相關(guān)信息傳入,之后交由Play框架處理。Play框架會根據(jù)該請求的內(nèi)容,查找相應(yīng)的路由規(guī)則,根據(jù)路由規(guī)則調(diào)用相應(yīng)的事件處理流程,并(一般來說會)最終向用戶返回結(jié)果,完成一次事件處理:

    ? 圖1. 事件流向

    用戶請求處理流程相關(guān)類

    作為一個web應(yīng)用框架,Play框架的最基本的功能就是響應(yīng)用戶請求。

    在本小節(jié)中,將概要講述當(dāng)一個用戶請求(request)到來時,play將啟動怎樣的流程來對該請求進(jìn)行處理,并最終返回相應(yīng)給用戶。

    本小節(jié)的重點在于闡明流程,由于這一流程涉及到M-V-C三個方面,對于其具體實現(xiàn)細(xì)節(jié),將在后文敘述。

    相關(guān)類介紹

    在介紹處理流程之前,需要介紹在這里流程中涉及到的一些類。本小節(jié)將重點介紹這些類的類結(jié)構(gòu),以及它們在這個流程中發(fā)揮的主要作用。

    9.1 PlayHandler

    PlayHandler繼承了org.jboss.netty.channel.SimpleChannelUpstreamHandler,用于在管道中處理服務(wù)器監(jiān)聽到的用戶請求。

    類圖如下:

    圖2 PlayHandler類圖

    其中比較重要的就是

    messageReceived(final ChannelHandlerContext ctx,final MessageEvent messageEvent)

    方法。該方法為對父類同名函數(shù)的重寫(Override)。父類提供這個函數(shù),由子類提供各自的具體實現(xiàn)。

    當(dāng)有消息到來的時候,服務(wù)器調(diào)用handler類的messageReceived函數(shù),利用多態(tài),將執(zhí)行不同的實現(xiàn)。

    在HttpServerPipelineFactory.getPipeline()中,當(dāng)每次需要獲得pipeline時,新建一個PlayHandler的實例,注冊到pipeline中。

    因此,每次的請求都會對應(yīng)一個新的PlayHandler實例,接著PlayHandler.messageReceiveed()方法被調(diào)用以處理用戶請求。PlayHandler.messageReceived()方法執(zhí)行過程將在后文敘述。

    9.2 Invoker與Invocation

    Play框架采用了命令模式(Command pattern),用于在多線程多任務(wù)情況下調(diào)度任務(wù),

    命令模式的原型如下:

    ? 圖3 命令模式原型

    在命令模式中,Client調(diào)用Invoker,往其中添加命令。

    Command代表了一個命令,開發(fā)者繼承Command并實現(xiàn)一個具體的命令,提交給Invoker進(jìn)行執(zhí)行。

    Invoker負(fù)責(zé)管理這些命令,在合適的時候執(zhí)行它們,并提供處理結(jié)果。

    命令模式把請求一個操作的對象與知道怎么執(zhí)行一個操作的對象分割開

    如此一來,開發(fā)者只需要關(guān)注命令的實現(xiàn),而不需要關(guān)注何時、如何執(zhí)行該命令。

    在Play框架中,Invoker及其內(nèi)部類Invocation實現(xiàn)了命令模式,Invocation相當(dāng)于Command類,

    由子類實現(xiàn)其execute方法(在這里表現(xiàn)為run()方法)。它們的類圖如下:

    圖4 Invoker類圖

    圖5 Invocation及DirectInvocation類圖

    在Invoker中,主要是invoke方法與invokeInThread方法。

    前者使用(ScheduledThreadPoolExecutor) executor調(diào)度線程執(zhí)行Invocation;

    后者直接在當(dāng)前線程中執(zhí)行任務(wù),當(dāng)執(zhí)行不成功時,等待一段時間之后重新執(zhí)行。

    Invoke方法還提供了另一個版本的重載函數(shù),可以在等待一段時間之后再執(zhí)行當(dāng)前任務(wù):invoke(final Invocation invocation, longmillis)。

    關(guān)于java.util.concurrent.ScheduledThreadPoolExecutor:繼承自java.util.concurrent.ThreadPoolExecutor,用于在給定的延遲后執(zhí)行命令,或者定期執(zhí)行命令,當(dāng)需要多個輔助線程,或者要求ThreadPoolExecutor具有額外的靈活性或功能時,此類要優(yōu)于Timer。

    Invoker.invoke(Invocation)的代碼如下,可以看到executor是如何調(diào)度Invocation的:

    publicstatic Future<?> invoke(final Invocation invocation) {Monitor monitor = MonitorFactory.getMonitor("Invokerqueue size", "elmts.");monitor.add(executor.getQueue().size());invocation.waitInQueue = MonitorFactory.start("Waiting forexecution");returnexecutor.submit(invocation);} publicvoid run() {if (waitInQueue != null) {waitInQueue.stop();}try {preInit();if (init()) {before();execute();after();onSuccess();}} catch (Suspend e) {suspend(e);after();} catch (Throwable e) {onException(e);} finally {_finally();}}

    在Invocation中實現(xiàn)了模板方法模式(Template method pattern),定義了run()方法的執(zhí)行步驟,

    而將各個步驟的實現(xiàn)方式交由其子類實現(xiàn),以此方式來完成命令模式中的自定義命令。

    模板方法模式原型如下:

    圖3-6 模板方法模式原型

    在Play框架的模板方法模式具體實現(xiàn)中,Invocation實現(xiàn)了Runnable接口,實現(xiàn)了run()方法,

    代碼如下:

    run()方法即為模板方法,在run()方法中,按順序調(diào)用了init(),before(),execute(),after(),onSuccess()等方法,

    這些方法需要由Invocation的子類實現(xiàn),從而實現(xiàn)不同的功能。通過這樣的設(shè)計,將執(zhí)行過程分解為多個部分,

    每個部分由子類實現(xiàn),而各部分的執(zhí)行順序由父類規(guī)定。

    在下文中,將提到PlayHandler.NettyInvocation,即為Invocation的一個子類。

    9.3 Result類

    Result繼承自RuntimeException,封裝了視圖層渲染結(jié)果(可能是HTML文件,也可能是XML文件或者二進(jìn)制文件等),

    類繼承關(guān)系如下:

    圖3-7 Result類繼承結(jié)構(gòu)圖

    FastRunTimeException是在Play框架中定義的一個可以快速實例化并拋異常類(Exception),

    主要是修改了fillInStackTrace()方法,使其直接返回null,從而實現(xiàn)快速實例化:

    public Throwable fillInStackTrace() {returnnull;}

    在Result中,提供了apply()方法,該方法需要其子類重寫,實現(xiàn)將相應(yīng)的內(nèi)容輸出的功能。

    在Result之下,play提供了多個Result的子類,用于執(zhí)行對不同的相應(yīng)的操作,其中比較常見的是RenderTemplate類,實現(xiàn)了對模板文件進(jìn)行最后輸出的功能。

    將Result作為一個Exception并以try/catch的形式來捕獲Result,而不是用返回值的方式,這是Play框架比較奇特的一點。

    這不符合通常對“異常(Exception)”的看法——一般來說,只有程序出現(xiàn)不可預(yù)知的情況的時候,才會使用try/catch代碼塊來捕獲Exception。然而,將渲染結(jié)果當(dāng)做異常拋出并捕捉,將簡化代碼,由Java自身決定執(zhí)行過程,提高了開發(fā)者的開發(fā)效率;

    另外,在處理用戶請求過程中,多處地方可能直接返回結(jié)果(如直接返回404頁面),

    框架在處理過程中將所有的處理結(jié)果歸到同一個地方并統(tǒng)一作判斷和處理,

    如果將結(jié)果作為返回值,則需要更繁復(fù)的方法來對結(jié)果進(jìn)行收集和處理。

    9.4 Template類

    Template類提供了對模板文件的封裝,并實現(xiàn)了對模板文件的編譯與執(zhí)行。

    類繼承關(guān)系如下:

    圖3-8 Template類繼承結(jié)構(gòu)圖

    最終實際使用的是GroovyTemplate類,該類代表一個模板文件,

    提供對頁面進(jìn)行渲染(將template文件轉(zhuǎn)換為html)的方法(render(Map<String, Object>))。

    在前面討論的RenderTemplate中,在構(gòu)造函數(shù)中傳入一個(Template)template(即一個RenderTemplate“擁有”一個Template實例),

    并執(zhí)行

    this.content = template.render(args);

    調(diào)用Template.render(args)將渲染結(jié)果保存在content中。

    在一個請求處理流程中,依次會經(jīng)歷PlayHandler->Invoker->Invocation->Result->Template

    10 play框架的攔截器

    ? Play框架的攔截器

    在控制器里,定義攔截器方法。攔截器將被控制器類及后代的所有action調(diào)用。

    這些方法必須是static的,但不能是public的,并使用有效的攔截注釋。

    @Before

    用@Before注釋的方法將在控制器的每個action前被調(diào)用執(zhí)行

    public class weixinIntercept extends Controller{@Before(unless="login") //""中寫的是方法的具體位置,例:wechat.account.wechatInformationstatic void check(){if(session.get("user") == null)login();} } /*可以使用unless和only,還可以使用@After,@Before,@Finally注釋 */

    控制器繼承

    如果一個控制器類是其他控制器類的子類,那么會按照繼承順序應(yīng)用于相應(yīng)的子類。

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

    public class Security extends Controller{@Beforeprotected static void checkAuthentic(){if(!session.containsKey("user"){unAuthen();}} }//另一個控制器 @With(Security.class) public class Admin extends Controller{...... }

    11 play源碼分析

    ==Play源代碼分析1—Server啟動過程==

    ? Play是個Rails風(fēng)格的Java Web框架,需要了解背景請看:

  • Play Framework介紹1–主要概念
  • Play Framework介紹2—Helloworld
  • 如何調(diào)試請看此處。以下進(jìn)入正題_

    Server啟動過程主要涉及三個地方:

  • play.Play類:代表Play本身業(yè)務(wù)模型。
  • play.server.Server類:負(fù)責(zé)服務(wù)器啟動。
  • play.classloading包:負(fù)責(zé).java文件讀取、編譯和加載。
  • 11.1總體流程

    Server.main為入口方法:

    public static void main(String[] args) throws Exception {Play.init(root, System.getProperty("play.id", ""));if (System.getProperty("precompile") == null) {new Server();} else {Logger.info("Done.");} }

    做兩件事:

  • Play.init
  • 然后創(chuàng)建Server對象。
  • Play.init

    public static void init(File root, String id) {readConfiguration();Play.classes = new ApplicationClasses();// Build basic java source pathVirtualFile appRoot = VirtualFile.open(applicationPath);roots.add(appRoot);javaPath = new ArrayList<VirtualFile>(2);javaPath.add(appRoot.child("app"));javaPath.add(appRoot.child("conf"));// Build basic templates pathtemplatesPath = new ArrayList<VirtualFile>(2);templatesPath.add(appRoot.child("app/views"));// Main route fileroutes = appRoot.child("conf/routes");// Load modulesloadModules();// Enable a first classloaderclassloader = new ApplicationClassloader();// PluginsloadPlugins();// Done !if (mode == Mode.PROD ||preCompile() ) {start();}}

    主要做:

  • 加載配置
  • new ApplicationClasses();加載app、views和conf路徑到VirtualFile中,VirtualFile是Play內(nèi)部的統(tǒng)一文件訪問接口,方便后續(xù)讀取文件
  • 加載route
  • 加載Module,Play的應(yīng)用擴展組件。
  • 加載Plugin,Play框架自身的擴展組件。
  • 工作在產(chǎn)品模式則啟動Play.
  • 關(guān)鍵步驟為new ApplicationClasses(),執(zhí)行computeCodeHashe(),后者觸發(fā)目錄掃描,搜索.java文件。

    相關(guān)過程簡化代碼如下:

    public ApplicationClassloader() { super(ApplicationClassloader.class.getClassLoader()); // Clean the existing classes for (ApplicationClass applicationClass : Play.classes.all()) {applicationClass.uncompile(); } pathHash = computePathHash();}int computePathHash() { StringBuffer buf = new StringBuffer(); for (VirtualFile virtualFile : Play.javaPath) {scan(buf, virtualFile); } return buf.toString().hashCode(); }void scan(StringBuffer buf, VirtualFile current) {if (!current.isDirectory()) {if (current.getName().endsWith(".java")) {Matcher matcher = Pattern.compile("\\s+class\\s([a-zA-Z0-9_]+)\\s+").matcher(current.contentAsString());buf.append(current.getName());buf.append("(");while (matcher.find()) {buf.append(matcher.group(1));buf.append(",");}buf.append(")");}} else if (!current.getName().startsWith(".")) {for (VirtualFile virtualFile : current.list()) {scan(buf, virtualFile);}} }

    11.2 Start流程

    簡化代碼如下:

    public static synchronized void start() { try { ... // Reload configuration readConfiguration();...// Try to load all classes Play.classloader.getAllClasses();// Routes Router.detectChanges(ctxPath);// Cache Cache.init();// Plugins for (PlayPlugin plugin : plugins) {try {plugin.onApplicationStart();} catch(Exception e) {if(Play.mode.isProd()) {Logger.error(e, "Can't start in PROD mode with errors");}if(e instanceof RuntimeException) {throw (RuntimeException)e;}throw new UnexpectedException(e);} }...// Plugins for (PlayPlugin plugin : plugins) {plugin.afterApplicationStart(); }} catch (PlayException e) {started = false;throw e; } catch (Exception e) {started = false;throw new UnexpectedException(e); } }

    關(guān)鍵步驟為執(zhí)行Play.classloader.getAllClasses()加載app目錄中的類型。

    簡化代碼如下:

    public List<Class> getAllClasses() { if (allClasses == null) { allClasses = new ArrayList<Class>();if (Play.usePrecompiled) { ... } else { List<ApplicationClass> all = new ArrayList<ApplicationClass>();// Let's plugins play for (PlayPlugin plugin : Play.plugins) { plugin.compileAll(all); }for (VirtualFile virtualFile : Play.javaPath) { all.addAll(getAllClasses(virtualFile)); } List<String> classNames = new ArrayList<String>(); for (int i = 0; i < all.size(); i++) { if (all.get(i) != null && !all.get(i).compiled) { classNames.add(all.get(i).name); } }Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));for (ApplicationClass applicationClass : Play.classes.all()) { Class clazz = loadApplicationClass(applicationClass.name); if (clazz != null) { allClasses.add(clazz); } } ... } } return allClasses; }

    主要步驟:

  • plugin.compileAll,給所有plugin一次機會進(jìn)行自定義編譯。
  • Play.classes.compiler.compile(classNames.toArray(new String[classNames.size()]));編譯所有.java文件。編譯后的.class存儲在ApplicationClass中。內(nèi)部使用了eclipse的JDT編譯器。
  • loadApplicationClass,取出ApplicationClass中的.class加入List中返回。
  • **到此完成.java的加載。**相關(guān)對象關(guān)系如下圖:

    11.3 啟動HTTP服務(wù)

    ? 接著new Server()啟動HTTP服務(wù),監(jiān)聽請求

    簡化代碼如下:

    public Server() { ... if (httpPort == -1 && httpsPort == -1) {httpPort = 9000; } ... InetAddress address = null; try {if (p.getProperty("http.address") != null) {address = InetAddress.getByName(p.getProperty("http.address"));} else if (System.getProperties().containsKey("http.address")) {address = InetAddress.getByName(System.getProperty("http.address"));}} catch (Exception e) {Logger.error(e, "Could not understand http.address");System.exit(-1); }ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool()) ); try { if (httpPort != -1) {bootstrap.setPipelineFactory(new HttpServerPipelineFactory());bootstrap.bind(new InetSocketAddress(address, httpPort));bootstrap.setOption("child.tcpNoDelay", true);if (Play.mode == Mode.DEV) {if (address == null) {Logger.info("Listening for HTTP on port %s (Waiting a first request to start) ...", httpPort);} else {Logger.info("Listening for HTTP at %2$s:%1$s (Waiting a first request to start) ...", httpPort, address);} } else {if (address == null) {Logger.info("Listening for HTTP on port %s ...", httpPort);} else {Logger.info("Listening for HTTP at %2$s:%1$s ...", httpPort, address);} }}} catch (ChannelException e) {Logger.error("Could not bind on port " + httpPort, e);System.exit(-1); } ... }

    主要步驟:

  • 設(shè)置端口,地址
  • new ServerBootstrap,創(chuàng)建jboss netty服務(wù)器。Play1.1.1使用了netty作為底層通訊服務(wù)器。
  • new HttpServerPipelineFactory(),設(shè)置netty所需的請求處理管道工廠。它負(fù)責(zé)當(dāng)請求到達(dá)時提供處理者。
  • bootstrap.bind(new InetSocketAddress(address, httpPort),綁定地址,端口。
  • 總結(jié)

    以上是生活随笔為你收集整理的26Play框架教程2学习笔记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。