日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

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

编程问答

26Play框架教程2学习笔记

發(fā)布時(shí)間:2023/12/14 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 26Play框架教程2学习笔记 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
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 簡(jiǎn)單的無狀態(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)建項(xiàng)目
    • 3.3 運(yùn)行程序
    • 3.4 配置數(shù)據(jù)庫
    • 3.5 補(bǔ)充
    • 3.6 將play項(xiàng)目導(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 虛擬主機(jī)
    • 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 高級(jí)HTTP綁定
        • 5.2.2.1 簡(jiǎn)單類型
        • 5.2.2.2 日歷類型
        • 5.2.2.3 文件類型
        • 5.2.2.4 數(shù)組和集合類型
        • 5.2.2.5 POJO對(duì)象綁定
      • 5.2.3 JPA對(duì)象綁定
      • 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 更簡(jiǎn)單方式
      • 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 國(guó)際化(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實(shí)現(xiàn)
    • 8.2 Bootstrap Job
      • 8.2.1 應(yīng)用啟動(dòng)
      • 8.2.2 應(yīng)用停止
    • 8.3 Scheduled Job
    • 8.4 Job的直接調(diào)用
  • 9 play框架的請(qǐng)求處理流程
    • 9.1 PlayHandler
    • 9.2 Invoker與Invocation
    • 9.3 Result類
    • 9.4 Template類
  • 10 play框架的攔截器
  • 11 play源碼分析
    • 11.1總體流程
    • 11.2 Start流程
    • 11.3 啟動(dòng)HTTP服務(wù)

1 play框架01

? play框架01–介紹

1.1 概述

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

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

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

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

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

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

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

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

1.2.4 HTTP到代碼的映射

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

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

即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àn)獒槍?duì)這些問題它都提供了非常優(yōu)秀的解決方案。

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

? 1.框架接收HTTP請(qǐng)求。

? 2.路由組件找到最匹配的規(guī)則,接受并處理請(qǐng)求,隨后調(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)簽庫的時(shí)候需要如此多的配置文件?為什么不能直接訪問底層的模型對(duì)象?JSP中太多的限制確實(shí)讓開發(fā)者感到失望,受JSP啟發(fā)又不被其約束,Play框架提供了自定義的模板引擎機(jī)制。

開發(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主要使用模板機(jī)制來渲染HTML,當(dāng)然也可以生成其他的文檔格式,比如e-mail messages,JSON等等。

1.2.6 內(nèi)置JPA支持

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

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

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

此外,實(shí)體如果繼承Play提供的play.db.jpa.Model類,操作代碼將會(huì)更加簡(jiǎn)潔,更加美觀:

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è)計(jì)受到實(shí)際Java Web開發(fā)的啟發(fā),包含了所有創(chuàng)建主流Web應(yīng)用所需要的工具:

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

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

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

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

  • 基于OpenID的分布式用戶信息驗(yàn)證。

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

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

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

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

1.2.8 Play的特性總結(jié)

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

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

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

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

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

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

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

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

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

  • 2 play框架02

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

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

    2.1 app目錄

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

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

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

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

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

    2.2 conf目錄

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

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

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

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

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

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

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

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

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

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

    2.3 public 的目錄

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

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

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

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

    2.4 lib目錄

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

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

    build.sbt file:

    這個(gè)文件是整個(gè)項(xiàng)目添加依賴包的地方,所有的依賴都寫在這里。

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

    2.5 project目錄

    這個(gè)目錄包含了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)建項(xiàng)目

    3.1 安裝Play

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

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

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

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

    3.2 創(chuàng)建項(xiàng)目

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

    打開一個(gè)新的命令行并敲入:

    ~$ play new yabe

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

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

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

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

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

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

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

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

    ? 第一個(gè),Play會(huì)自動(dòng)監(jiān)測(cè)Java源代碼的改變并在運(yùn)行時(shí)自動(dòng)重載。

    ? 第二個(gè),當(dāng)一個(gè)Java異常發(fā)生時(shí),Play能向你展示更好的錯(cuò)誤報(bào)告 - 帶對(duì)應(yīng)的源代碼的哦~

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

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

    3.3 運(yùn)行程序

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

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

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

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

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

    打開routes文件,會(huì)看到第一個(gè)“route”:

    GET / Application.index

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

    在這個(gè)程序中,Application.index是controllers.Application.index簡(jiǎn)寫,因?yàn)閏ontrollers包是隱式附加的。

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

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

    Play程序則有多個(gè)入口方法,每個(gè)URL就有一個(gè)。這些方法稱為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類,這個(gè)類提供了許多controller需要的方法,比如在index action中的render方法。

    index action定義為public static void,因?yàn)閏ontroller類不需要實(shí)例化和返回值。

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

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

    模板是在/app/views 目錄下的簡(jiǎn)單文本文件。

    因?yàn)檫@里沒有指定一個(gè)模板,index action會(huì)使用一個(gè)默認(rèn)的模板:Application/index.html。

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

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

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

    #{extends /} tag 表示這個(gè)模板繼承于main.html這個(gè)模板。模板繼承可用來創(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怎么自動(dòng)加載它。

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

    public static void index() { render() }

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

    但是因?yàn)閏ontroller有錯(cuò)誤,所以在瀏覽器中顯示一個(gè)編譯錯(cuò)誤。

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

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

    在瀏覽器刷新這個(gè)頁面。

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

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

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

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

    db=em

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

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

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

    INFO ~ Connected to jdbc:h2:mem:play

    3.5 補(bǔ)充

    如果運(yùn)行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項(xiàng)目導(dǎo)入myeclipse

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

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

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

    ? 然后輸入play eclipsify +項(xiàng)目名

    第三步:導(dǎo)入

    4 play框架04

    4.1 路由

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

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

    ? 。請(qǐng)求路徑(比如/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é)議支持以下所列的方法,用于指定客戶請(qǐng)求服務(wù)器的動(dòng)作,其中GET和POST是最為常用的兩種方法:

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

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

    如果在路由文件中指定*作為HTTP方法,那么這個(gè)路由會(huì)匹配任何HTTP請(qǐng)求:

    * /clients/{id} Clients.show

    使用上述的路由配置,以下兩個(gè)HTTP請(qǐng)求都會(huì)被框架接受:

    GET /clients/1541 PUT /clients/1212

    4.1.2 URI表達(dá)式

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

    /clients/all

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

    /clients/all

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

    /clients/{id}

    ? 則可以分別匹配:

    /clients/12121

    ? 和

    /clients/toto

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

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

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

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

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

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

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

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

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

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

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

    GET /clients Clients.index

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

    GET /clients/? Clients.index

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

    4.1.3 定義Java調(diào)用

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

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

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

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

    GET /admin admin.Dashboard.index

    4.1.4 404作為Action

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

    比如:

    # 忽略favicon請(qǐng)求 GET /favicon.ico 404

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

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

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

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

    GET /pages/{id} Application.page

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

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

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

    ? 所以被作為默認(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)簽對(duì)model類型進(jìn)行迭代,為每種類型生成控制器路由定義。

    以后文章會(huì)詳細(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會(huì)按照聲明的順序,優(yōu)先選擇最先聲明的路由,比如:

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

    ? 在上例的路由配置中,雖然請(qǐng)求/clients/all可以同時(shí)匹配這兩條路由配置,

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

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

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

    ? 以下路由規(guī)則匹配2個(gè)大寫字母以及3-4個(gè)數(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)客戶端請(qǐng)求/public/*路徑時(shí),Play會(huì)從應(yīng)用的public文件夾中獲取相應(yīng)的靜態(tài)資源。這里的優(yōu)先級(jí)與標(biāo)準(zhǔn)路由配置一樣適用。

    4.1.8 staticFile:mapping

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

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

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

    4.1.9 虛擬主機(jī)

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

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

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

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

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

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

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

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

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

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

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

    4.2 逆向生成URL

    Play路由器是按照J(rèn)ava調(diào)用生成URL的,所以可以將URI表達(dá)式都集中到同個(gè)配置文件中,使得重構(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ù)會(huì)被添加到查詢字符串中:

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

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

    4.3 設(shè)置content type

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

    該值通過文件擴(kuò)展名來決定使用何種視圖模板進(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請(qǐng)求的默認(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請(qǐng)求服務(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中獲取請(qǐng)求格式,動(dòng)態(tài)指定渲染的模板類型。參考如下路由配置:

    GET /index.{format} Application.index

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

    ? 請(qǐng)求為/index.txt時(shí),則會(huì)使用文本進(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ù)客戶端的請(qǐng)求類型,實(shí)現(xiàn)同個(gè)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中包含了通配符的值,這樣瀏覽器便會(huì)接受任何media類型。

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

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

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

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

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

    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)會(huì)使用index.html模板進(jìn)行渲染,因?yàn)闉g覽器發(fā)送了包含text/html的Accept header。

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

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

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

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

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

    Accept headerFormatTemplate file namemapping
    nullnullindex.htmlnull格式請(qǐng)求提供默認(rèn)模版擴(kuò)展
    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請(qǐng)求頭,為應(yīng)用選擇相應(yīng)的media類型來實(shí)現(xiàn)自定義格式。

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

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

    ? 如果檢查后發(fā)現(xiàn)請(qǐng)求頭中media類型為text/x-vcard時(shí),將調(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è)計(jì)的軟件架構(gòu)方式。REST定義了一些關(guān)鍵的規(guī)則:

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

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

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

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

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

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

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

    在控制器中,每個(gè)以public static聲明,返回值為void的方法稱為Action。

    Action的方法聲明如下:

    public static void action_name(params…);

    Play會(huì)自動(dòng)將HTTP請(qǐng)求參數(shù)轉(zhuǎn)化為與之相匹配的Action方法參數(shù),這部分內(nèi)容會(huì)在后面的獲取HTTP參數(shù)小節(jié)進(jìn)行詳細(xì)講解。

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

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

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

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

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

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

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

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

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

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

    5.2 獲取HTTP參數(shù)

    5.2.1 使用Map參數(shù)

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

    它包含了當(dāng)前所有HTTP請(qǐng)求的參數(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框架提供了自動(dòng)將Action聲明的參數(shù)與HTTP參數(shù)自動(dòng)匹配的功能(只需要保持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。

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

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

    如果參數(shù)含有多個(gè)值,那么可以定義數(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會(huì)將該參數(shù)設(shè)置為默認(rèn)值(通常情況下對(duì)象類型為null,原始數(shù)據(jù)類型為0)。

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

    5.2.2 高級(jí)HTTP綁定

    5.2.2.1 簡(jiǎn)單類型

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

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

    日期類型

    如果HTTP參數(shù)字符串符合以下幾種數(shù)據(jù)格式,框架能夠自動(dòng)將其轉(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í)慣對(duì)日期的格式做進(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); }

    在這個(gè)例子中,對(duì)于法語和德語的日期格式是dd-MM-yyyy,其他語言的日期格式是MM-dd-yyyy。

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

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

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

    date.format=yyyy-MM-dd

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

    5.2.2.2 日歷類型

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

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

    5.2.2.3 文件類型

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

    然后使用java.io.File類型提取文件對(duì)象:

    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)用的臨時(shí)文件下(Application_name/tmp)。

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

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

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

    第一個(gè)元素key值為name,value為John;

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

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

    5.2.2.5 POJO對(duì)象綁定

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

    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的實(shí)例,并將HTTP參數(shù)解析為該實(shí)例的屬性。如果出現(xiàn)參數(shù)無法解析或者類型不匹配的情況,會(huì)自動(dòng)忽略。

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

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

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

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

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

    5.2.3 JPA對(duì)象綁定

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

    一旦匹配成功,HTTP請(qǐng)求中的其他User屬性參數(shù)可以直接更新到數(shù)據(jù)庫相應(yīng)的User記錄中:

    public static void save(User user){user.save(); }

    和POJO映射類似,可以使用JPA綁定來更改對(duì)象,但需要注意的是必須為每個(gè)需要更改的對(duì)象提供id:

    user.id = 1 &user.name=morten &user.address.id=34 &user.address.street=MyStreet

    5.2.4 自定義綁定

    綁定機(jī)制支持自定義功能,可以按照讀者的需求,自定義參數(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注解還具有國(guó)際化支持,可以為每個(gè)本地化提供專門的注解:

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

    上例中的綁定使用逗號(hào)將字符串分隔成List。

    ? @play.data.binding.NoBinding

    @play.data.binding.NoBinding注解允許對(duì)不需要綁定的屬性進(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對(duì)象的isAdmin屬性始終不會(huì)被editProfile方法(Action)所修改,

    即使有惡意用戶偽造POST表單提交user.isAdmin=true信息,也不能修改user的isAdmin權(quán)限。

    play.data.binding.TypeBinder

    @As注解還提供完全自定義綁定的功能。自定義綁定必須是TypeBinder類的實(shí)現(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方法需要對(duì)客戶端作出HTTP響應(yīng),最簡(jiǎn)單的方法就是發(fā)送結(jié)果對(duì)象。當(dāng)對(duì)象發(fā)送后,常規(guī)的執(zhí)行流程就會(huì)中斷。

    以下面這段代碼為例,最后一句System.out.println的輸出不會(huì)被執(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對(duì)象,之后的其他語句將不會(huì)執(zhí)行,所以在控制臺(tái)中,

    并不會(huì)打印出“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)的格式化語法對(duì)輸出的文本進(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對(duì)此進(jìn)行了很好的封裝,

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

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

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

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

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

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

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

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

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

    5.3.3 返回XML字符串

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

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

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

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

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

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

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

    同樣的,這些序列化操作都不需要由開發(fā)者去做,全部交給Play就行,開發(fā)者需要做的就是按照規(guī)范簡(jiǎn)單地調(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ù)(如存儲(chǔ)在服務(wù)器里的文件、圖片等)給客戶端。

    以下代碼范例將會(huì)展示如何使用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控制器中使用時(shí),

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

    5.3.5 下載附件功能

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

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

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

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

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

    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é)合的方式來定義,

    比如在上述例子中,模板對(duì)應(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 更簡(jiǎn)單方式

    這里介紹一種更簡(jiǎn)單的方式向模板傳遞數(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í)傳遞多個(gè)參數(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(…)方法的第一個(gè)參數(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),可以將請(qǐ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,

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

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

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

    具體做法如下所示:

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

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

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

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

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

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

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

    具體范例如下:

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

    定義全局編碼格式

    通常情況下,整個(gè)應(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請(qǐng)求只能調(diào)用一個(gè)Action,

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

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

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

    具體實(shí)現(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請(qǐng)求;

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

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

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

    • HTTP響應(yīng)重定向?yàn)?#xff1a;/clients/3132;

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

    5.5 攔截器

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

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

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

    5.5.1 @Before

    使用@Before注解的方法會(huì)在每個(gè)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ù)對(duì)@After,@Before以及@Finally注解都適用。

    5.5.2 @After

    使用@After注解的方法會(huì)在每個(gè)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注解的方法就會(huì)執(zhí)行,且拋出的異常會(huì)以參數(shù)的形式傳遞到@Catch注解的方法中。

    具體實(shí)現(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異常處理程序一樣,捕獲父類往往可以獲得更多的異常類型。

    如果擁有多個(gè)需要捕獲的方法,可以通過指定優(yōu)先級(jí)來確定他們的執(zhí)行順序。具體實(shí)現(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注解的方法總是在每個(gè)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注解增加更多攔截器

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

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

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

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

    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請(qǐng)求之間保存數(shù)據(jù),可以將數(shù)據(jù)保存在Session或者Flash內(nèi)。

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

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

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

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

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

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

    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)閉瀏覽器后就會(huì)失效,除非修改配置文件中的application.session.maxAge屬性。

    設(shè)置方法如下:

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

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

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

    6 play框架06

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

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

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

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

    6.1 模板語法

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

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

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

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

    所有的動(dòng)態(tài)部分將會(huì)在模板的執(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分別是相對(duì)路徑還是絕對(duì)路徑。如: 1.首頁:生成指向首頁的鏈接。 2.@{’/public/stylesheets/main.css’}:引入CSS靜態(tài)資源文件。@{…} 和@@{…}
    國(guó)際化用于顯示經(jīng)過國(guó)際化處理后的消息內(nèi)容。&{…}
    注釋用于在模板中添加注釋。如:{ 這是注釋 }。{…}
    腳本用于添加復(fù)雜的 Groovy 腳本,可以聲明變量和執(zhí)行業(yè)務(wù)邏輯。%{…}%

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

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

    具體例子如下:

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

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

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

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

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

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

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

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

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

    #{script 'jquery.js' /}

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

    #{script 'jquery.js'/}

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

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

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

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

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

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

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

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

    Title

    ,在頁面輸出時(shí)會(huì)自動(dòng)進(jìn)行轉(zhuǎn)義:

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

    也可以通過調(diào)用擴(kuò)展方法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>

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

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

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

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

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

    clientName=The client name is %s

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

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

    6.1.5 注釋(comment):

    *{…}*使用*{…}*標(biāo)記的內(nèi)容會(huì)被模板引擎忽略,起到注釋作用:*{**** 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)置對(duì)象輸出動(dòng)態(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>

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

    6.2 模板繼承

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

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

    同時(shí)也能夠幫助開發(fā)人員實(shí)現(xiàn)頁面中動(dòng)態(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)容定義為公用元素,

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

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

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

    具體使用方法如下:

    #{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)用,模型類里面會(huì)頻繁地使用聲明為public的變量。即使是經(jīng)驗(yàn)尚淺的Java開發(fā)者,也懂得慎用public類型的變量。

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

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

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

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

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

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

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

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

    Play框架的模型部分會(huì)自動(dòng)生成getXxx/setXxx方法,保持代碼的簡(jiǎn)潔。

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

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

    上述代碼被框架載入后就會(huì)轉(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;} }

    因?yàn)樽兞勘宦暶鳛閜ublic,可以使用如下方式對(duì)屬性進(jìn)行操作:

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

    程序在加載時(shí)會(huì)自動(dòng)地轉(zhuǎn)換成:

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

    注意:

    因?yàn)檫@些getXxx/setXxx方法是在運(yùn)行時(shí)動(dòng)態(tài)生成的,所以不能直接調(diào)用。

    如果在編碼階段使用他們,編譯器會(huì)因?yàn)檎也坏皆摲椒ǘ鴪?bào)錯(cuò)誤。

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

    如果需要保護(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ù)值就會(huì)拋出異常:

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

    Play總會(huì)優(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;} }

    補(bǔ)充:

    @Entity注解的作用是通知Play自動(dòng)開啟JPA實(shí)體管理器,@Required是對(duì)該屬性的約束。

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

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

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

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

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

    通常情況下,開發(fā)者需要將模型對(duì)象持久化。最常用的方法是把這些數(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

    補(bǔ)充:

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

    將數(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ū)動(dòng)程序,存放在$PLAY_HOME/framework/lib/目錄下。

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

    Play可以連接任何JDBC兼容的數(shù)據(jù)庫,只需要將相應(yīng)的驅(qū)動(dòng)類庫添加到/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文件中用配置選項(xiàng)指定JPA方言:

    jpa.dialect=<dialect>

    補(bǔ)充:

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

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

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

    除了使用Hibernate外,在編碼時(shí)還可以直接從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)自動(dòng)地將Java對(duì)象持久化到數(shù)據(jù)庫。

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

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

    注意:

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

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

    也可以直接從play.db.jpa.JPA對(duì)象中得到實(shí)體管理器,通過實(shí)體管理器可以將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實(shí)例中CRUD操作進(jìn)行對(duì)象持久化:

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

    補(bǔ)充:ActiveRecord模式

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

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

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

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

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

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

    Play為了保持模型無狀態(tài)化,需要避免一些常見的陷阱,最重要的就是不要因?yàn)槎嗾?qǐng)求而將對(duì)象保存到Java堆中。

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

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

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

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

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

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

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

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

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

    • 在第一次請(qǐng)求時(shí)初始化對(duì)象并將它保存在緩存中。

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

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

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

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

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

    8 play框架08

    ? play框架08–Job異步處理

    8.1 Job實(shí)現(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,事實(shí)上Job可以返回任何類型的值。

    8.2 Bootstrap Job

    8.2.1 應(yīng)用啟動(dòng)

    Bootstrap Job,顧名思義就是在應(yīng)用開始時(shí)運(yùn)行的任務(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不需要任何返回值。

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

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

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

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

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

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

    注意:

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

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

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

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

    所以DEV模式下Job會(huì)延遲啟動(dòng)。

    8.2.2 應(yīng)用停止

    Web應(yīng)用停止或關(guān)閉的時(shí)候,常常也需要進(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();} }

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

    8.3 Scheduled Job

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

    例如:

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

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

    這時(shí)候可以使用@On注解指定時(shí)間點(diǎn)來執(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也是不需要任何返回值的,即使返回了也會(huì)丟失。

    補(bǔ)充:

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

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

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

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

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

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

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

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

    9 play框架的請(qǐng)求處理流程

    ? Play框架的請(qǐng)求處理流程

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

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

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

    ? 圖1. 事件流向

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

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

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

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

    相關(guān)類介紹

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

    9.1 PlayHandler

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

    類圖如下:

    圖2 PlayHandler類圖

    其中比較重要的就是

    messageReceived(final ChannelHandlerContext ctx,final MessageEvent messageEvent)

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

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

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

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

    9.2 Invoker與Invocation

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

    命令模式的原型如下:

    ? 圖3 命令模式原型

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

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

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

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

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

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

    由子類實(shí)現(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í)行不成功時(shí),等待一段時(shí)間之后重新執(zhí)行。

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

    關(guān)于java.util.concurrent.ScheduledThreadPoolExecutor:繼承自java.util.concurrent.ThreadPoolExecutor,用于在給定的延遲后執(zhí)行命令,或者定期執(zhí)行命令,當(dāng)需要多個(gè)輔助線程,或者要求ThreadPoolExecutor具有額外的靈活性或功能時(shí),此類要優(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中實(shí)現(xiàn)了模板方法模式(Template method pattern),定義了run()方法的執(zhí)行步驟,

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

    模板方法模式原型如下:

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

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

    代碼如下:

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

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

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

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

    9.3 Result類

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

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

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

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

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

    public Throwable fillInStackTrace() {returnnull;}

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

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

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

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

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

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

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

    9.4 Template類

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

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

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

    最終實(shí)際使用的是GroovyTemplate類,該類代表一個(gè)模板文件,

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

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

    并執(zhí)行

    this.content = template.render(args);

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

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

    10 play框架的攔截器

    ? Play框架的攔截器

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

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

    @Before

    用@Before注釋的方法將在控制器的每個(gè)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注釋 */

    控制器繼承

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

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

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

    11 play源碼分析

    ==Play源代碼分析1—Server啟動(dòng)過程==

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

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

    Server啟動(dòng)過程主要涉及三個(gè)地方:

  • play.Play類:代表Play本身業(yè)務(wù)模型。
  • play.server.Server類:負(fù)責(zé)服務(wù)器啟動(dòng)。
  • 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對(duì)象。
  • 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)用擴(kuò)展組件。
  • 加載Plugin,Play框架自身的擴(kuò)展組件。
  • 工作在產(chǎn)品模式則啟動(dòng)Play.
  • 關(guān)鍵步驟為new ApplicationClasses(),執(zhí)行computeCodeHashe(),后者觸發(fā)目錄掃描,搜索.java文件。

    相關(guān)過程簡(jiǎ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流程

    簡(jiǎn)化代碼如下:

    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目錄中的類型。

    簡(jiǎn)化代碼如下:

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

    11.3 啟動(dòng)HTTP服務(wù)

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

    簡(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所需的請(qǐng)求處理管道工廠。它負(fù)責(zé)當(dāng)請(qǐng)求到達(dá)時(shí)提供處理者。
  • bootstrap.bind(new InetSocketAddress(address, httpPort),綁定地址,端口。
  • 總結(jié)

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

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

    综合色亚洲 | 黄色av成人在线 | 色综合久久88色综合天天 | 播五月综合 | 国产第一福利 | 成年人黄色免费视频 | 久久全国免费视频 | 日韩电影久久 | 久久久久99精品成人片三人毛片 | 天天爱天天操天天干 | 久久婷婷网 | 国产精品久久久久久模特 | 亚洲精品高清一区二区三区四区 | 精品国产一区二区三区蜜臀 | 91精品啪在线观看国产81旧版 | 欧美在线18 | 欧美资源 | 亚洲狠狠丁香婷婷综合久久久 | adn—256中文在线观看 | 一区三区视频 | 黄色软件网站在线观看 | 欧美一级大片在线观看 | 91精品毛片| 国产精品自在欧美一区 | 精品国偷自产国产一区 | 国产特级毛片aaaaaa | 久久精品国产免费看久久精品 | 午夜视频亚洲 | 久久久久久久免费看 | 国产精品成人a免费观看 | 免费在线91 | 免费在线精品视频 | 国产+日韩欧美 | 黄色亚洲 | 激情开心站| 国产一级在线播放 | 色综合久久久久综合体桃花网 | 人操人| 久久久www成人免费毛片麻豆 | 丁香六月av | 日韩一区二区三区在线观看 | 五月天色综合 | 久久爱影视i | 久久精品一区二区三区国产主播 | 九九电影在线 | 国产成人一二三 | 夜夜干夜夜 | 亚洲精品久久久久久国 | 99精品免费久久久久久日本 | 久久久久久久久久亚洲精品 | 亚洲精品久久久久久国 | 亚洲成人网av | 亚洲视频一级 | 久久九精品| 久久老司机精品视频 | 狠狠狠狠狠操 | 九九综合久久 | 国产免费资源 | 国产玖玖在线 | 在线观看你懂的网站 | 在线视频第一页 | 中文字幕一区二区在线播放 | 国产成人精品综合久久久 | 99久久这里有精品 | 欧美大片大全 | 亚洲国产成人精品在线观看 | 欧美在线aa| 天天看天天干天天操 | 天天操天天色天天 | 96av麻豆蜜桃一区二区 | 91精品免费 | 精品国产综合区久久久久久 | 欧美福利视频一区 | 久久综合欧美精品亚洲一区 | 欧美日韩免费视频 | 欧美 亚洲 另类 激情 另类 | 国产精品成人av电影 | 久久精品国产免费观看 | 国产一级二级视频 | 欧美在线free | 欧美日韩国产在线一区 | 在线播放一区 | 婷婷综合成人 | 亚洲欧美视频一区二区三区 | 91丨porny丨九色| 亚洲日本欧美在线 | 在线有码中文字幕 | 五月婷婷六月丁香 | 国产精品毛片久久久久久久 | 91专区在线观看 | 在线精品视频免费观看 | 区一区二区三在线观看 | 欧美成天堂网地址 | 国产成人亚洲在线观看 | 综合激情婷婷 | 国产精品美女久久久久久免费 | 婷婷5月激情5月 | 欧美午夜精品久久久久久孕妇 | 欧美视频不卡 | 日韩欧美一区二区在线播放 | 亚洲激情在线 | 麻豆精品视频 | 91在线免费播放 | www.五月激情.com | 日韩高清免费在线 | 人人玩人人添人人澡97 | 亚洲一级黄色大片 | www.日日操.com| 丁香六月久久综合狠狠色 | 久久伦理影院 | 激情五月伊人 | 亚洲成人网在线 | 天天射天天干天天插 | 日韩黄色免费在线观看 | 亚洲少妇久久 | 特及黄色片 | 一区二区三区动漫 | 国产精品a久久久久 | 丝袜+亚洲+另类+欧美+变态 | 91精品久久香蕉国产线看观看 | 亚洲精品视频在线观看免费 | 欧美 日韩 性 | 99色在线观看 | 日韩精品一区二区在线观看视频 | a√资源在线 | 人人爽人人射 | 久久96 | 在线观看aaa | 国产精品白丝jk白祙 | 特级毛片在线免费观看 | 在线欧美a | 成人黄色影片在线 | 狠狠操夜夜操 | 亚洲一级黄色片 | 久久久国产精品一区二区三区 | 中文电影网 | 国产精品久久在线观看 | 中文字幕亚洲综合久久五月天色无吗'' | 国际精品久久久 | 在线免费色| av大片网址 | 久精品在线 | 激情综合五月婷婷 | 在线观看国产中文字幕 | 亚洲综合一区二区精品导航 | 免费a网站| 韩日视频在线 | 久久影院中文字幕 | 国产一区国产二区在线观看 | 国产 在线 高清 精品 | 国产高清永久免费 | 国产精品丝袜久久久久久久不卡 | 精品久久在线 | 一区二区三区免费看 | 中文字幕乱码在线播放 | 欧美日韩亚洲第一 | av久久久久久 | 97国产精品亚洲精品 | 日本女人b | 91亚洲精品乱码久久久久久蜜桃 | 九九热在线视频免费观看 | 亚洲精品久久久久www | 91精品国产91热久久久做人人 | www..com黄色片 | 成年人电影免费在线观看 | 婷婷综合导航 | 男女啪啪视屏 | 亚洲一区日韩在线 | 81精品国产乱码久久久久久 | 伊人天天综合 | 亚洲精选在线 | 免费a网站| 在线观看亚洲精品 | www.午夜| 久久免费在线 | 亚洲乱码久久久 | 欧美成天堂网地址 | 久久精视频 | 亚洲精品乱码久久久久久蜜桃欧美 | www国产一区 | 久久久99精品免费观看app | 日韩在线观看精品 | 久草网视频 | 国产亚洲永久域名 | 天天射天天操天天 | 亚洲黄色小说网 | 日韩电影一区二区三区在线观看 | 日韩一区二区免费视频 | 国产热re99久久6国产精品 | 国产精品久久久久久久久毛片 | 青青久草在线 | 久久久久久久国产精品影院 | 久久网站最新地址 | 日韩网站在线免费观看 | 国产精品一区二区中文字幕 | 欧美性生活小视频 | 黄色特级一级片 | 91热视频 | 欧美日韩国产在线一区 | 最近最新中文字幕 | 亚洲欧美视频一区二区三区 | 91看片淫黄大片一级在线观看 | av网站地址| 亚洲日本va在线观看 | 岛国一区在线 | 91av九色| 日韩免费观看视频 | 精品一区二区亚洲 | 国产视频69 | 国产精品原创在线 | 高清av中文字幕 | 国产综合精品一区二区三区 | 一区二区三区韩国免费中文网站 | 天天爱天天舔 | 国产精品一区二区三区在线播放 | 久久人人爽爽人人爽人人片av | 夜夜夜精品 | 97在线看片 | 6699私人影院| 免费色视频在线 | 99久久久| 国产在线视频一区二区三区 | 国产成人一区二区三区在线观看 | 国产在线欧美在线 | 国产中文伊人 | 超碰人人国产 | 国产99在线免费 | 成年人免费观看国产 | 手机色站| 超级av在线 | 久久不卡日韩美女 | 日本天天操 | 色婷婷在线视频 | 国产 欧美 日韩 | 丁香九月婷婷 | 免费视频99 | 精品久久1 | 伊人伊成久久人综合网站 | 日本黄区免费视频观看 | 国产资源精品在线观看 | 日韩深夜在线观看 | 亚洲成人资源 | 免费看三级网站 | 欧美激情视频一区二区三区 | 一级片视频在线 | 欧美视频不卡 | 欧产日产国产69 | 精品国产诱惑 | 91精品国产成 | av在线看网站 | 在线之家官网 | 91成人小视频 | 91激情视频在线播放 | 久久99久久99精品免视看婷婷 | 中文免费在线观看 | av在线一级 | 五月天婷婷丁香花 | 午夜精品中文字幕 | 99电影456麻豆 | 国产精品va在线观看入 | 久草在线观看视频免费 | 成人av电影在线 | 婷婷.com| 一级免费片 | 国产亚洲在线 | 成人av在线观 | 美女视频黄免费网站 | 美女黄频在线观看 | 国产在线播放一区二区三区 | 丁香色婷婷| 欧美一区三区四区 | av在线h| 99精彩视频在线观看免费 | 午夜视频在线观看一区二区三区 | 色狠狠操 | 免费观看久久 | 一区二区三区在线视频观看58 | 最新国产在线观看 | 久久精品久久久久电影 | 爱色av.com| 亚洲精品视频在线 | 日韩在线播放av | 成人精品一区二区三区中文字幕 | 人人爽人人片 | 深爱婷婷激情 | 2000xxx影视| 视频国产在线 | 国产精品www | 水蜜桃亚洲一二三四在线 | 日韩精品一区二区免费视频 | 亚洲激情婷婷 | 亚洲精品在线视频网站 | 精品久久国产一区 | 国产超碰在线 | 免费观看一区二区三区视频 | 狠狠操天天操 | 天天曰天天爽 | 在线播放国产精品 | 你操综合 | 香蕉视频国产在线 | 黄色在线观看免费 | 特级毛片aaa | 一区在线电影 | 欧美日韩精品在线免费观看 | 日韩精品一区二区在线观看视频 | 激情亚洲综合在线 | 激情视频网页 | 免费视频三区 | 黄色大片网 | 日日夜夜天天 | 日韩午夜av | 久久久天堂 | 色操插 | av三区在线 | 日韩在线高清 | 91香蕉视频黄色 | 中文字幕在线免费看 | 丝袜护士aⅴ在线白丝护士 天天综合精品 | 成年人免费看片网站 | 欧美999| 亚洲欧美日韩国产一区二区三区 | 2018好看的中文在线观看 | 久久精品视频日本 | 一区 二区电影免费在线观看 | 午夜精品中文字幕 | 九九国产视频 | 综合网久久 | 久久狠狠一本精品综合网 | 一区二区三区 亚洲 | 97超碰在线久草超碰在线观看 | 久久九九视频 | 久久成人国产精品入口 | 激情 一区二区 | 91桃色免费观看 | 在线蜜桃视频 | 久久综合加勒比 | 国产精品久久久久久久久久久久午夜片 | 久久av电影 | 日韩精品免费一区二区三区 | 婷婷开心久久网 | 91三级视频| 欧美日韩后 | 日韩免费播放 | 五月婷婷视频在线观看 | 国产精品99免费看 | 国产精品欧美一区二区 | 人人爱在线视频 | 91 在线视频 | 黄色小说在线免费观看 | 人人爱人人射 | 亚在线播放中文视频 | 国产一级免费电影 | 久久av伊人 | 99成人精品| 午夜视频免费播放 | 又粗又长又大又爽又黄少妇毛片 | 丁香花在线视频观看免费 | 久久精品艹 | 97人人模人人爽人人喊网 | 国产一区视频在线 | 久久资源总站 | av在线电影免费观看 | 国产日产亚洲精华av | 国产精品色婷婷 | 日韩av一区在线观看 | 99精品视频免费在线观看 | 国产精品99久久久久的智能播放 | 在线看免费 | 国产婷婷视频在线 | 国产精品v欧美精品v日韩 | 91精品婷婷国产综合久久蝌蚪 | 高清国产午夜精品久久久久久 | 日本成人免费在线观看 | 中文字幕在线观看91 | 天天干天天草天天爽 | 久久美女免费视频 | 成人毛片在线观看 | 91人人爱 | 国产三级国产精品国产专区50 | av中文资源在线 | 色综合色综合久久综合频道88 | 久久久免费国产 | 一区二区视频在线看 | 五月天狠狠操 | 91在线看 | 中文字幕电影高清在线观看 | 久久综合中文色婷婷 | 欧美一区二区视频97 | 亚洲www天堂com | 狠狠撸电影| 国产剧情一区二区在线观看 | 午夜在线免费视频 | 91精品国产高清自在线观看 | 色婷婷久久久 | 91精品在线免费 | 国产最新在线视频 | 在线观看视频在线 | 欧美九九视频 | 欧美怡红院 | 免费观看久久 | 在线观看久久 | 福利视频网址 | 亚洲极色 | 欧美精品九九99久久 | 久久视频在线 | 国产区精品视频 | 国产精彩视频 | av在线精品| 亚洲专区免费观看 | 久久精品直播 | 天天综合在线观看 | 欧美精品免费一区二区 | 91福利视频免费观看 | 丁香婷婷在线 | 永久免费毛片在线观看 | 久爱精品在线 | 成人a在线观看高清电影 | 91精品导航 | 国产色资源 | 国内精品久久久精品电影院 | 亚洲第一中文字幕 | 亚州国产精品久久久 | 色资源中文字幕 | 欧美午夜视频在线 | 三级视频片 | 九九三级毛片 | 精品欧美一区二区精品久久 | 青青河边草免费 | 天天操天天草 | 欧美精品久久久久久久久老牛影院 | 日韩日韩日韩日韩 | 国产小视频你懂的在线 | 久久精品久久99 | 精品在线视频一区二区三区 | 91桃色视频 | 国产精品婷婷午夜在线观看 | 午夜精品久久久99热福利 | 色狠狠干| 国产精品嫩草69影院 | 国产亚洲午夜高清国产拍精品 | 成人久久综合 | 久久国产免费视频 | 欧美一级黄色片 | 91精品国产亚洲 | 婷婷视频在线观看 | 久久精品人人做人人综合老师 | 在线小视频你懂的 | 91精选在线 | 国产香蕉久久精品综合网 | 激情亚洲综合在线 | 一区二区电影在线观看 | 中日韩在线| 国产精品久久婷婷六月丁香 | 日韩高清网站 | 国产色道| 99久久99久国产黄毛片 | а天堂中文最新一区二区三区 | 免费观看一区二区三区视频 | 国产精品va在线播放 | 精品久久久久免费极品大片 | 色资源二区在线视频 | 国产中年夫妇高潮精品视频 | 国产精品理论视频 | 看av免费网站 | 国产精品久久久久久久免费 | 91插插影库 | 不卡的av电影在线观看 | 亚洲精品在线网站 | 免费看污在线观看 | 精品美女久久 | 亚洲精品久久久蜜臀下载官网 | 99re亚洲国产精品 | 婷婷丁香激情五月 | 夜夜操夜夜干 | 在线激情影院一区 | 国产精品毛片一区二区在线 | 国产1区2区 | 久久激情视频免费观看 | 99久久日韩精品免费热麻豆美女 | 四虎在线观看 | 97精品视频在线播放 | 五月天国产精品 | 人人爽人人爽人人片av免 | 成人免费视频视频在线观看 免费 | 日韩精品久久久免费观看夜色 | 久久手机视频 | av天天色| 午夜影院在线观看18 | 久久视频网 | 欧美日韩伦理一区 | 黄色亚洲 | 91精品入口 | 男女精品久久 | 日韩av成人在线 | 天天曰夜夜爽 | 国产99久久久国产精品免费二区 | 中文字幕亚洲欧美日韩2019 | 九九九热精品 | 久热色超碰 | 日韩成人精品一区二区三区 | 免费日韩 | 日韩精品偷拍 | 九色福利视频 | 久久久久久久久网站 | www.久久色 | 国产精品一级视频 | 日日摸日日添夜夜爽97 | 精品在线99 | 免费看特级毛片 | 精品国产免费一区二区三区五区 | 女人18毛片a级毛片一区二区 | 欧美福利在线播放 | 9999精品免费视频 | 欧美亚洲一级片 | 国产成人一二三 | 久久精品欧美一区二区三区麻豆 | 99久e精品热线免费 99国产精品久久久久久久久久 | 欧美成人影音 | 丁香激情五月婷婷 | 日韩在线观看中文 | 久久成人高清视频 | 国产色一区 | 欧美美女视频在线观看 | 亚洲成人av电影在线 | 人交video另类hd | 中文字幕精品一区二区精品 | 成人av电影免费在线播放 | 久久视频免费在线 | 国产精品美女久久久久久久久 | 成人午夜影视 | 9在线观看免费高清完整版 玖玖爱免费视频 | 成人在线播放网站 | 四虎在线免费观看 | 国产精品99久久久久久人免费 | 国产成人高清av | 香蕉成人在线视频 | 国产xxxx做受性欧美88 | 色综合久久综合网 | 一区二区在线不卡 | 99人成在线观看视频 | 欧美少妇xxxxxx | 91精品视频在线 | av在线免费播放网站 | 久久电影日韩 | 国产成人精品一区二区三区在线观看 | 国产资源网 | 91免费看片黄 | 国产视| 西西444www大胆高清视频 | 国内精品美女在线观看 | 黄色免费高清视频 | 亚洲一区二区三区四区在线视频 | 国产私拍在线 | 中文一区在线观看 | 婷婷亚洲五月色综合 | 成人免费在线播放视频 | 中文字幕人成不卡一区 | av免费电影在线 | 午夜久久电影网 | 91色国产 | 欧美日韩性视频在线 | 97超碰成人| 亚洲va在线va天堂va偷拍 | 国产精品免费久久久 | 香蕉视频久久久 | 国产又粗又硬又长又爽的视频 | 久久电影国产免费久久电影 | 欧美日韩高清一区 | 一本色道久久综合亚洲二区三区 | 五月婷婷毛片 | 亚洲视频一区二区三区在线观看 | 最近高清中文在线字幕在线观看 | 亚洲最大成人网4388xx | 免费看片亚洲 | 国产精品久久久久永久免费观看 | 黄在线免费看 | 国产精品久久久999 国产91九色视频 | 国产一区二区久久久 | 久久xx视频 | 91成版人在线观看入口 | 黄色99视频 | 成人欧美一区二区三区在线观看 | 天天射天天色天天干 | 69成人在线| 国产成人精品午夜在线播放 | 国产一区二区在线免费 | 五月天丁香综合 | 91禁在线观看| 亚洲成人在线免费 | 狠狠色噜噜狠狠狠合久 | 中文字幕免费一区 | 在线日本看片免费人成视久网 | 国产区高清在线 | 伊人手机在线 | 91看片在线免费观看 | 亚洲综合国产精品 | 人人爽人人爽人人爽人人爽 | 国产成人一区二区在线观看 | 国内精品久久久久久久久久久 | av在线精品 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 色综合久久久久久久久五月 | 麻豆精品视频在线观看免费 | 毛片永久免费 | 在线视频观看亚洲 | 日韩亚洲在线视频 | 99精品免费在线观看 | 最新99热| 精品久久国产一区 | 久久久久久久久艹 | 欧洲一区二区在线观看 | 在线免费观看视频一区二区三区 | 亚洲激情 欧美激情 | 精品国产一二三四区 | 亚洲成人高清在线 | 精品久久久网 | 色噜噜在线观看 | 99久久9 | av黄色在线播放 | 久久久久激情 | 夜夜骑天天操 | 国产91区 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 亚洲精品小区久久久久久 | 99欧美视频 | 亚洲老妇xxxxxx | 国产精品18久久久久久久久久久久 | 中文字幕高清免费日韩视频在线 | 久久激情综合网 | 人人爽人人干 | 国产午夜在线 | 超黄视频网站 | 国产精品热视频 | 日本h在线播放 | 亚洲黄污| 又黄又爽又无遮挡免费的网站 | 久久久麻豆视频 | 国产黄色视 | 亚洲精品午夜国产va久久成人 | 黄网站色视频 | 欧美精品一区二区免费 | 在线观看免费视频 | 在线观看视频一区二区 | 美女黄频视频大全 | 免费看黄在线 | 精品久久久99 | 992tv在线观看网站 | 午夜精品久久久久久久99无限制 | 九九九九九精品 | 97狠狠干| 天天色欧美 | 中文字幕在线看人 | 日韩美一区二区三区 | 六月丁香在线视频 | 色视频在线观看免费 | 亚洲三级av | 久一久久 | 97人人人人| 日韩美女一级片 | 欧美在线视频不卡 | 日韩av免费在线看 | 国产精品你懂的在线观看 | 91中文字幕在线视频 | 欧美一区二区在线免费看 | 成人毛片网 | 日韩| 九九久久精品 | 欧美亚洲xxx| 最新国产在线视频 | 又污又黄的网站 | 亚洲综合日韩在线 | 国产中文视频 | 国产成人在线播放 | 国产91精品久久久久 | 91人人在线 | 国产免费三级在线观看 | 99色婷婷 | 在线观看 国产 | 亚洲精品一区二区精华 | 91在线视频网址 | 日韩精品中文字幕在线 | 久久五月婷婷丁香 | 四虎影视精品成人 | 亚洲国产成人av网 | 中文字幕 在线看 | 国产黄色在线观看 | 91pony九色丨交换 | 欧美在线观看视频一区二区 | 久久国产成人午夜av影院宅 | 奇米影视四色8888 | 国产黄色av网站 | 少妇搡bbbb搡bbb搡忠贞 | 天海翼一区二区三区免费 | 中文字幕在线观看视频网站 | 337p日本大胆噜噜噜噜 | 久久九九久久精品 | 久久99精品久久久久久三级 | 看av在线 | 天天天天爱天天躁 | 深夜免费福利视频 | 免费在线观看污 | 玖玖999| 国产一级二级av | 国产原创在线 | 国产在线精品观看 | 精品一区二三区 | 天天射天天操天天干 | 色综合天天在线 | 精品一二三区 | 久久人视频 | 免费视频二区 | 麻豆视频国产精品 | 99热最新在线 | 欧美一级性生活片 | 午夜国产福利在线 | 亚洲精品资源在线观看 | av经典在线| 在线免费黄色毛片 | 免费在线观看国产黄 | av成人在线电影 | 免费在线观看日韩 | 中文字幕在线播放第一页 | 日本中文在线播放 | 黄色小说视频网站 | 天天操夜夜做 | 久久久www免费电影网 | 91精品在线免费视频 | 二区视频在线观看 | 久久在线观看视频 | 成人在线观看影院 | 免费黄a| 久久成人免费 | 99re中文字幕 | 国产精品久久久久久久久久直播 | 麻豆免费在线视频 | av在线影视| 99re视频在线观看 | 久久国产福利 | 亚洲精品 在线视频 | www.伊人色.com | 国产一区在线观看免费 | 亚洲一区二区精品3399 | 欧美在线视频精品 | 国产短视频在线播放 | 成人 亚洲 欧美 | 97超碰在线久草超碰在线观看 | 99精品视频观看 | 亚洲一级片 | 日韩啪视频| 天天摸夜夜添 | 亚洲片在线资源 | 国产精品自产拍在线观看网站 | 国产一区免费 | 久久久影院官网 | 欧美精品久久久久久久久久 | 一区二区三区四区免费视频 | 久久免费高清视频 | 久久综合激情 | 亚洲视频电影在线 | 亚洲天堂视频在线 | 高清视频一区二区三区 | 最近最新最好看中文视频 | 亚洲国产一区av | 成年人av在线播放 | 国产无限资源在线观看 | 国产高清视频免费在线观看 | 亚洲japanese制服美女 | 美女网站视频久久 | 国产中文在线播放 | av色综合| 在线а√天堂中文官网 | 四虎欧美 | 久久久久久网 | 久久精品欧美一区 | 亚洲成人黄色 | 国内视频在线观看 | 伊人五月综合 | 色www永久免费 | 国产精品九九九九九 | 视频国产区 | 久99久精品视频免费观看 | 密桃av在线 | 国产色婷婷精品综合在线手机播放 | 日本久久免费电影 | 天天射天天干 | 久久爱www.| 亚洲精品黄色片 | 亚洲成人精品在线 | 精品国偷自产国产一区 | 久草在线在线 | 五月开心六月婷婷 | 精品二区视频 | 色爱成人网 | 毛片的网址 | 国偷自产视频一区二区久 | 欧美一区二区三区激情视频 | 国产亚洲一区二区在线观看 | 91精品婷婷国产综合久久蝌蚪 | 国产精品午夜免费福利视频 | 欧美午夜性生活 | 免费日韩一区二区三区 | 久草在线视频中文 | 99爱精品在线 | 青草视频在线免费 | 麻豆一区在线观看 | 中文字幕网址 | 不卡日韩av | 天天色.com | 久草在线视频在线 | 日日干日日操 | 久久久高清免费视频 | av网站播放 | 成人免费看片98欧美 | 91成年视频 | 亚在线播放中文视频 | 国产丝袜高跟 | 精品一区二区亚洲 | 亚洲闷骚少妇在线观看网站 | 亚洲欧美日韩一区二区三区在线观看 | 中国一级片视频 | 亚洲区视频在线观看 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 天天色天天| 国产成人免费观看 | 96精品高清视频在线观看软件特色 | 久久人人爽爽人人爽人人片av | 国产中文字幕三区 | av在线短片| 久久久久一区二区三区 | 丁香激情网 | 天天艹天天 | 国产精品18久久久久白浆 | 国产精品美女在线观看 | 久久草av| 国产精品伦一区二区三区视频 | 日本性xxxxx 亚洲精品午夜久久久 | 免费久久视频 | 久久久资源网 | 丁香花中文在线免费观看 | 精品久久一区二区 | 人人爽人人插 | 日韩二区三区在线观看 | 国产成人免费在线 | 亚洲美女在线国产 | 亚洲精品女 | 色婷婷久久久综合中文字幕 | 国产无套精品久久久久久 | 国产视频二区三区 | 97成人超碰 | 91精品久久久久久综合五月天 | 精品一区二区三区四区在线 | 国产又粗又猛又色 | 国产不卡免费视频 | 人人射人人爽 | 午夜精品三区 | 国产护士av | 在线看一区二区 | 五月天丁香综合 | 日女人免费视频 | 天天艹日日干 | 极品久久久久久久 | 玖玖视频国产 | 天天色天天艹 | 91九色九色| 麻豆91在线 | 黄色小网站免费看 | 99在线热播精品免费 | 国产精品一区二区三区四 | 在线视频电影 | 国产福利av在线 | 亚洲综合精品视频 | 婷婷视频在线观看 | 激情综合色播五月 | 91视频在线免费观看 | 欧美做受高潮 | 成人午夜在线电影 | 绯色av一区| 免费情缘 | 91九色蝌蚪视频在线 | 精品综合久久 | 日韩精品不卡在线观看 | 亚洲精选视频免费看 | 久久精品这里热有精品 | se婷婷| 精品福利在线 | 欧美日韩久久一区 | 精品国内自产拍在线观看视频 | www.亚洲视频| 日日干天夜夜 | 手机在线观看国产精品 | 日韩和的一区二在线 | 精品国产视频在线观看 | 91自拍成人 | 最近中文字幕完整高清 | 在线观看视频免费播放 | 久久成人国产 | 日韩专区视频 | 日本中文字幕在线电影 | 国产经典 欧美精品 | 夜夜躁日日躁 | 97超碰国产精品女人人人爽 | 插婷婷 | 在线看国产日韩 | 婷婷五天天在线视频 | 9在线观看免费高清完整版在线观看明 | 成人一级 | 国产成人精品日本亚洲999 | 精品一区免费 | 亚洲伊人成综合网 | 欧美日韩大片在线观看 | 亚洲国产手机在线 | 免费久久久久久久 | 亚洲成a人片77777kkkk1在线观看 | 日韩在线视频一区二区三区 | 日韩av五月天 | 99r精品视频在线观看 | 国产一级在线观看视频 | 久久久www成人免费毛片麻豆 | 久草网站在线观看 | 欧美在线a视频 | 天天干人人 | 色婷婷综合视频在线观看 | 日本公妇色中文字幕 | 欧洲激情在线 | 免费日韩一级片 | 日韩视频免费 | 日韩欧在线 | 天天天操天天天干 | 精品在线小视频 | 国产精品 中文在线 | 色多多污污在线观看 | 成年人免费在线播放 | 国产精品免费视频网站 | av电影 一区二区 | 亚洲黄色成人网 | 中文字幕在线视频国产 | 精品视频在线免费 | 色综合久久五月 | 日韩av在线高清 | 狠狠狠狠狠狠操 | 中文字幕在线播出 | 丁香六月久久综合狠狠色 | 青青河边草观看完整版高清 | 麻豆一级视频 | 91在线免费观看国产 | 超碰在线97观看 | 伊人色综合久久天天网 | 韩国av在线播放 | 午夜.dj高清免费观看视频 | 黄污视频网站大全 | 狠狠干五月天 | 免费中文字幕在线观看 | 久草视频在线资源 | 久久尤物电影视频在线观看 | 国产精品久久久久久久久久妇女 | 国产精品女同一区二区三区久久夜 | 日韩中文字幕a | 国产精品99久久久久久有的能看 | 天天草综合网 | 亚洲精品乱码久久久久 | 超碰在线cao| 免费网站v | sesese图片 | 亚洲国产精品视频 | 999久久久免费精品国产 | 久久久人人爽 | 久久99婷婷 | 国产精品一区二区麻豆 | 综合在线观看色 | 综合久久精品 | 欧美精品中文字幕亚洲专区 | 色综合久久久久综合体桃花网 | av一区二区三区在线播放 | 免费av片在线 | 91久久人澡人人添人人爽欧美 | 96国产精品视频 | 波多野结衣在线播放一区 | 九九九免费视频 | 国产91欧美 | 国产成人一级 | 国产精品九九视频 | 日韩在线观看小视频 | 亚洲一级黄色大片 | 五月天欧美精品 | 91大神精品视频在线观看 | 在线观看黄色大片 | 久久婷婷综合激情 | 日韩精品一区二区三区高清免费 | 亚洲无吗av| 国产精彩在线视频 | 麻豆视频在线观看免费 | 黄色一级片视频 | 天天插天天狠 | 国内精品久久久久久久久久 | 国产九九九视频 | 日韩草比 | 色欧美成人精品a∨在线观看 | 免费久久99精品国产婷婷六月 |