Play!Framework學(xué)習(xí)筆記:初識Play
自我介紹:
- 小弟是一名二年級學(xué)生,目前正在企業(yè)里做Intern,實習(xí)內(nèi)容是JAVA后臺方面的。
發(fā)文動因:
- 公司的一項很好的有獎新技術(shù)調(diào)研比賽活動,獎都是其次,更重要的是有了一個這樣的平臺,有了一定的壓力,可以促使自己把平時的一些念頭借此良機落實(肯定 很多人和我一樣,看到些新技術(shù)總會有些想法,比如等有空了做做例子,把源碼讀一讀,但是隨著時間的推移,這份熱情伴隨著生活瑣事也許就消逝了。。。。 o(╯□╰)o)
- 之前學(xué)習(xí)Linux,入門時看鳥哥系列( linux.vbird.org/ )非常的高效,也很欣賞他樸實踏實的文風(fēng),由淺入深,有條有理。因此自己也希望能寫出個大部分人能看懂,且不覺得枯燥的東西來。
發(fā)文目的:
- 自我學(xué)習(xí):實習(xí)了大半年,在現(xiàn)行框架上做過些coding的工作,但是對框架的理解還是皮毛,而且在企業(yè)里待著,身邊每一位都是技術(shù)牛人,覺得壓力巨大,所以需要不斷學(xué)習(xí)來盡量彌補這巨大的差異,盡量使自己畢業(yè)時能達到在軟件行業(yè)從業(yè)的水平??蚣芊矫婕夹g(shù)之前還學(xué)習(xí)過Spring和Spring Security,但是也是僅限于能參照指南做些相應(yīng)的應(yīng)用而已,因此想借為期50天的PLAY調(diào)研機會,順便橫向比較學(xué)習(xí)其他框架。通過寫博客,給別人看是一方面,更重要的是寫東西過程本身就是對學(xué)習(xí)的總結(jié)和加深記憶的很好方式。
- 指導(dǎo)學(xué)弟:我在剛?cè)胄械臅r候就很茫然,知道要多編程,多讀經(jīng)典源碼,但是往往是事倍功半,說白了就是沒上道,沒開竅,當(dāng)時特別希望有個入行不太深,但是多少知道點,又勤于分享的學(xué)長能有個對于閱讀源碼,做例子之類手把手的教程,而小弟正巧目前處于此階段,過了懵懂,但是了解也不深,趁著記憶猶存,盡量把自己在之前學(xué)習(xí)遇到的不解問題和困難在學(xué)習(xí)Play!時再解釋一遍,非常希望能幫助處于入門階段的學(xué)弟學(xué)妹們O(∩_∩)O哈哈~
- 參加公司的技術(shù)調(diào)研比賽:不求名次,重在參與,希望我這種裹腳布的敘述方式能給公司眾編程高手達人一種不同的技術(shù)表達風(fēng)格。我初衷是盡量用最二(傻瓜,通 俗,簡單)的語言和圖案把PLAY說明白。
- 喜歡寫博:雖然有論文的壓力,但是說實話我討厭死寫論文,非要用些自己都覺得惡心的專業(yè)術(shù)語描述一些本應(yīng)該很有活力的東西。我相信中國的大部分論文寫出來,很多都是自己都不想多看幾遍的,別再說別人了。而且寫博可以閑暇時一個人待在宿舍聽著音樂喝著茶用閑暇活動慢慢寫,很悠閑,很享受。
困 惑:
- 最困惑的莫過于官網(wǎng)的指導(dǎo)不但詳細(xì)而且傻瓜,且用到的英語也很簡單,這么寫下去可能大部分照搬官網(wǎng),顯得很沒意義。而自己也沒有足夠的經(jīng)驗和知識來評判Play!的優(yōu)劣。所以唯一有點價值的可能就是稍微深入一下,爭取把Play!的內(nèi)部機理和部分源碼搞明白。
- 由于小弟資歷尚淺,學(xué)藝不精,文中難免出錯,望各位大師如果耐心觀看此文,發(fā)現(xiàn)錯誤請指正?。》浅8兄x??!
目標(biāo)一:學(xué)習(xí)官網(wǎng)Getting Started和Run Demo
@
學(xué)弟學(xué)妹醒目,要了解一個框架,第一件事就是運行最簡單的例子“HELLO WORLD”,(喂!喂!學(xué)長,你不會以為我們是白癡吧。。。#_#)。別懷疑,其實往往跑最簡單的例子能更加迅速的了解框架的大致工作原理,這里也說一下的是,在學(xué)校的學(xué)習(xí)時,我們大多習(xí)慣于,想學(xué)一門技術(shù)時,先去圖書館找一本厚厚的教材,書名字中經(jīng)常會帶著“精通 專家 大全 權(quán)威”之類的忽悠人的詞匯,儼然如葵花寶典版通讀后立馬能成為大師的感覺,但是書的質(zhì)量先不說,單純看書,這種學(xué)習(xí)技術(shù)知識的方式就如空中筑樓,基于幻想的學(xué)習(xí),其實從實際動手入手,多體會,再看看一些經(jīng)典書籍,加深理解才是比較好的途徑. |
1.1 基本概念和特點
不免落俗,先說些基本概念和特點
基本都是照搬官網(wǎng)的,所以也可以自己去看
http://www.playframework.org/documentation/1.0.2/home
概念:Play framework是個輕快的REST風(fēng)格J2EE FULL-STACK框架
RESTFul:可以參考一下這篇文章 www.infoq.com/cn/articles/rest-introduction ,暫且可以不關(guān)注。 Full-stack:現(xiàn)在很流行的SSH(Struts+Spring+Hibernate)的整合就是Full-Stack框架,及能提供J2EE開發(fā)全套功能的框架。比較出名的Full-Stack框架還有ruby on rails。 |
一些優(yōu)點:
From a Introduction post 寫道
- Play is fast. So damn fast.
- No configuration. Not even one.
- Play is fun and joyful to develop web applications.
- Update your code and refresh your browser. Yes, you don't need to restart the server.
- It doesn't use maven. It uses Python.
- Play uses Groovy for the template system.
- Play Routing is amazingly easy.
- Play is heavily influenced by Rails, Django and Grails.
- Testing your code is easy.
- Play easily integrates with Eclipse, NetBeans, Intellij IDEA and TextMate.
稍作解釋:
No configuration是指沒有web.xml等配置文件(比如:如果自己組合SSH,得配置web.xml , spring和struts的配置文件,要配很多bean,注入以及過濾器)。
框架使用的應(yīng)用服務(wù)器支持熱加載,寫好代碼后,框架再編譯后直接將類加載到服務(wù)器中,不需要重啟服務(wù)器,這就大大提高了工作效率。
Routing非常簡單,類似windows的hosts文件,定義了HTTP請求和應(yīng)用程序的映射,再第一個例子中可以看到。
測試工作變得簡單,是因為Play提供了良好的測試框架,在例子中可以看到。
這里有個大概印象即可,后面通過例子的講解會逐一解釋。
1.2 準(zhǔn)備工作
原文在此 : www.playframework.org/documentation/1.0.2/firstapp
- 安裝JDK 1.5以上
- 下載Play! 1.02 http://download.playframework.org/releases/play-1.0.2.1.zip
- 解開壓縮包,將play的根目錄添加至環(huán)境變量的Path
1.3 創(chuàng)建默認(rèn)項目
- 打開命令行,切換至你希望放置Hello world項目的目錄,輸入 “play new helloworld”,根據(jù)提示再輸入一遍項目名。
- 這樣項目就創(chuàng)建好了,然后輸入“play run helloworld”,將我們新創(chuàng)建的項目先啟動起來看一下。
- 打開瀏覽器,輸入http://localhost:9000,我們先看一下默認(rèn)的創(chuàng)建的項目是什么樣子的。
- Play!真是很貼心,默認(rèn)的項目啟動后顯示的內(nèi)容是告訴你這個框架大概 是 怎么運行的。
我們對照默認(rèn)頁面的說明,看看生成的項目目錄里都有些什么:
打開項目目錄helloworld,我們可以看到如上圖的目錄結(jié)構(gòu)
- app目錄:放置了java文件,可以看到app目錄下還有三個目錄controllers,models和views,目錄結(jié)構(gòu)非常清晰,分別存放MVC模式的三層的源碼。
- pig345 評注 // 2010/5/24
文章里面有處用詞稍微不當(dāng):“MVC模式的三層的源碼”
我理解,MVC模式中 M、V、C 是3類平等的組件, 一定要和 3層模式中的 表現(xiàn)層、邏輯層、持久層,相區(qū)別,此處應(yīng)酌情改下,否則容易暗示讀者(尤其是你說的學(xué)弟學(xué)妹)把3層模式和MVC模式混為一談。 - conf目錄:放置了Play的配置文件,其中:
- application.conf是Play!框架的核心功能配置文件,比如配置DB,應(yīng)用程序端口號等基礎(chǔ)性應(yīng)用程序配置都在此。
- routes 是配置http請求與該請求調(diào)用的應(yīng)用程序方法之間的映射
Routes代碼:
- # Routes
- # This file defines all application routes (Higher priority routes first)
- # ~~~~
- # Home page
- GET / <span style="color: #ff0000;"><strong>Application.index</strong>
- </span>
- # Map static resources from the /app/public folder to the /public path
- GET /public/ staticDir:public
- # Catch all
- * /{controller}/{action} {controller}.{action}
Java代碼:
- package controllers;
- import play.mvc.*;
- public class Application extends Controller {
- public static void index() {
- render();
- }
- }
如上所示,當(dāng)我們用GET方法訪問應(yīng)用程序根目錄(localhost:9000/)時,框架從router中找到匹配的映射,即 Application.index 所對應(yīng)的調(diào)用方法是controllers目錄下的Application類的index()方法。
index()方法中調(diào)用了render()方法,這個方法將顯示模板(template)app/views/Application/index.html。
再來看看index.html
Html代碼:
- #{extends 'main.html' /}
- #{set title:'Home' /}
- #{welcome /}
#{extends 'main.html' /}表示該模板繼承自'main.html'
#{welcome /}Tag則是顯示默認(rèn)頁面的主體部分。
其中:app/view/main.html
Html代碼:
- <span style="font-weight: normal;"><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'}">
- #{get 'moreStyles' /}
- <link rel="shortcut icon" type="image/png" href="@{'/public/images/favicon.png'}">
- <script src="@{'/public/javascripts/jquery-1.4.2.min.js'}" type="text/javascript" charset="utf-8"></script>
- #{get 'moreScripts' /}
- </head>
- <body>
- #{doLayout /}
- </body>
- </html></span>
可以看到main.html里定義了一些共通的html元素,如所用的css,shortcut icon圖標(biāo),字符集和js庫等。
通過繼承的方式,可以使得模板的復(fù)用性大大增強。
#{doLayout /} Tag指示index.html的內(nèi)容是插入此處的。
1.4 將默認(rèn)項目改寫成第一個app:Hello world
編輯helloworld/app/views/Application/index.html
Html代碼:
- #{extends 'main.html' /}
- #{set title:'Home' /}
- <form action="@{Application.sayHello()}" method="GET">
- <input type="text" name="myName" />
- <input type="submit" value="Say hello!" />
- </form>
與默認(rèn)項目的index區(qū)別,去掉了welcome tag,添加了一個form,注意action部分
@{}的用途是讓Play自動生成能夠Invoke Application.sayHello action的URL"@{Application.sayHello()}" method="GET"
我們將其轉(zhuǎn)換成routes的表達方式是: GET /Application/sayHello Application.sayHello
保存后我們重新訪問 localhost:9000/
出錯的原因是“No route able to invoke action Application.sayHello was found.”
因為route找不到 Application.sayHello action。
這里我們還能看到Play友好的出錯信息,一目了然。
此處可能你會奇怪,我們在routes里沒有寫明GET /Application/sayHello Application.sayHello的映射,那么route又怎么知道去invoke Application.sayHello action呢?
回頭看routes,最后兩行定義了 # Catch all * /{controller}/{action} {controller}.{action} 這是一個共通的匹配 因為@{Application.sayHello()}實質(zhì)上時轉(zhuǎn)化成url:/Application/sayHello 而*能匹配所有Http方法,因此route這里對應(yīng)的action自然是Application.sayHello |
在/app/controllers/Application.java中添加sayHello()方法。
Java代碼:
- package controllers;
- import play.mvc.*;
- public class Application extends Controller {
- public static void index() {
- render();
- }
- public static void sayHello(String myName) {
- render(myName);
- }
- }
sayHello仍然是調(diào)用render方法,但是與index方法的區(qū)別是這里傳了一個參數(shù)myName。
保存類文件后我們重新訪問 localhost:9000/
找到對應(yīng)的action后,index顯示出來了,隨便輸入名字點擊按鈕后,又顯示錯誤 ...+_+
提示找不到模板,我們需要寫與Action對應(yīng)的模板Application/sayHello.html
觀察此時URL: http://localhost:9000/application/sayhello?myName=Darcy 點擊‘Say Hello’按鈕后,URL跳轉(zhuǎn)到如我們先前所說的/application/sayhello,并且?guī)蠀?shù)myName=Darcy。 route通過此URL找到匹配的action后Invoke application.sayhello("Darcy") 將參數(shù)傳入render("Darcy"). 而render所作的工作,通過出錯提示可以猜測到是需要模板sayhello.html來顯示相應(yīng)的PAGE. 其實這里Play幫我們做了很多工作,不需要我們一 一配置。 關(guān)于MVC:類Application繼承自play.mvc.Controller,在MVC中處于控制器。 控制器的作用將數(shù)據(jù)模型(model)和視圖(view)聯(lián)系起來。 說白了最簡單的用途是怎樣將數(shù)據(jù)模型傳遞到相應(yīng)頁面上。 本例我們在app/models沒有類,并不意味著我們沒有model,其實myName就是個簡單的model,當(dāng)我們在文本框輸入后并點擊按鈕后,myname被傳遞到render里,下一步自然是要將myName傳遞到視圖層,此處我們沒顯式的指定傳到哪個視圖,其實Play默認(rèn)做法是 Application中的方法名與模板名是一致的。 |
創(chuàng)建模板文件helloworld/app/views/Application/sayHello.html
Html代碼:
- #{extends 'main.html' /}
- #{set title:'Home' /}
- <h1>Hello ${myName ?: 'guest'}!</h1>
- <a href="@{Application.index()}">Back to form</a>
保存文件后我們重新訪問 localhost:9000/
現(xiàn)在可以正常訪問了。
我們看看參數(shù)myName在模板里是如何得到的。
如上面的紅字部分${myName ?: 'guest'},直接用同樣的變量名即可訪問,真是非常簡單!!
但是您肯定會疑惑,這種表達式${myName ?: 'guest'}以及之前的#{extends 'main.html' /}到底是什么語言....?
通過聲明此表達式,能創(chuàng)建一個模板的動態(tài)元素,這個表達式最終顯示的值是動態(tài)取得的,如本例${myName ?: 'guest'},myName是個動態(tài)元素,其名稱與控制器Application中與本模板關(guān)聯(lián)的相應(yīng)方法中的參數(shù)名一致,myName后面的'?' 是判斷myName是否存在(不為null),如果存在,則${myName ?: 'guest'}=‘Darcy‘(以我之前輸入Darcy為例),否則${myName ?: 'guest'}= ‘guest’(不輸入內(nèi)容直接點按鈕)
#{tagName /}通過tag名和參數(shù)完成模板功能,比如#{extends 'main.html' /} extends是tag名,‘main.html’是參數(shù),模板引擎讀到#{extends 'main.html' /}后會幫我們完成繼承main.html模板的功能。這些tag是在模板引擎中定義好的。 |
現(xiàn)在的URL( localhost:9000/application/sayhello?myName=darcy )看起來不是很舒服,我們可以在routes里對他優(yōu)化一下。
在默認(rèn)route前加上我們自定義的route
在routes文件中,是靠上的r oute 優(yōu)先。
Route代碼:
- GET /hello Application.sayHello
保存一下再跑一次看看,URL變了 : localhost:9000/hello?myName=ddd
自定義一些頁面元素
編輯helloworld/app/views/main.html
將<body>稍作修改
Html代碼:
- <body>
- The Hello world app.
- <hr/>
- #{doLayout /}
- </body>
保存后查看
這里我們可以再次看到,main作為一個父類模板,將一些共通的頁面元素寫在main里,其他繼承于他的模板都會有這些元素,大大提高了復(fù)用性,因此,我們在自己用Play做東西時,也應(yīng)該靈活使用此特性。
添加基本校驗(Adding validation)
編輯helloworld/app/controllers/Application.java
Java代碼:
- package controllers;
- import play.mvc.*;
- import play.data.validation.*;
- public class Application extends Controller {
- public static void index() {
- render();
- }
- public static void sayHello(@Required String myName) {
- if(validation.hasErrors()) {
- flash.error("Oops, please enter your name!");
- index();
- }
- render(myName);
- }
- }
除了修改sayHello方法,別忘了import play.data.validation.*
annotation @Required的用途是校驗myName是否存在。若驗證失敗(validation.hasErrors() == ture) ,則拋出錯誤信息(flash.error("Oops, please enter your name!");。
這些錯誤信息是存放于Play的flash scope
from Play org
The flash scope allows to keep messages during action redirection.
flash scope能夠在redirection時保存消息。
為了顯示錯誤信息,我們還需添加顯示錯誤信息的代碼,由于這個錯誤信息是在Redirect至index后顯示的,因此我們編輯index.html
編輯helloworld/app/views/Application/index.html
Html代碼:
- #{extends 'main.html' /}
- #{set title:'Home' /}
- #{if flash.error}
- <p style="color:#c00">
- ${flash.error}
- </p>
- #{/if}
- <form action="@{Application.sayHello()}" method="GET">
- <input type="text" name="myName" />
- <input type="submit" value="Say hello!" />
- </form>
注意看#{if flash.error},這是Play中if Tag的用法,后面跟著Boolen參數(shù)。
flash雖然沒作為參數(shù)傳至render,但是也傳遞過來了,這就是flash scope的作用。
保存后我們訪問主頁,不填值,直接點按鈕。
出錯信息正常顯示。
添加自動化測試
Play的測試框架繼承JUnit和Selenium等,使寫測試代碼也非常方便。
因為本例沒什么邏輯,所以我們只能進行一些頁面的測試。
這里我們寫一個Selenium測試腳本進行頁面的測試。
首先,我們需要切換到測試模式。
到CMD中關(guān)閉應(yīng)用模式服務(wù)器(ctrl+c)
使用命令:
play test helloworld
切換到測試模式。
打開瀏覽器訪問http://localhost:9000/@tests
這個頁面是play測試框架的控制臺,可以選擇要進行測試的項目并執(zhí)行測試,查看測試結(jié)果等。
我們將三項都選擇,然后點Start進行測試,結(jié)果都是綠燈。原因是此時的測試代碼都是必通過的= =#
比如:assertEquals(2, 1 + 1);
從此控制臺還能看出,Play的測試框架可以進行單元測試,功能測試和Selenium web測試,真是太方便了??!
Selenium 是HTML的腳本,有點冗余,Play在測試框架中對Selenium 腳本也進行了優(yōu)化,及可以使用Play的模板來寫Selenium 腳本
(此處我想應(yīng)該是Play對html腳本進行了二次封裝,轉(zhuǎn)成Play模板,當(dāng)Selenium 讀測試腳本是,Play的模板引擎會將模板再還原成Selenium 腳本,這里順便標(biāo)記一下,以后通過看Play的具體實現(xiàn)來驗證)。
編輯現(xiàn)成的Selenium 腳本:helloworld/test/Application.test.html
Html代碼:
- #{selenium}
- // Open the home page, and check that no error occurred
- open('/')
- assertNotTitle('Application error')
-
- // Check that it is the form
- assertTextPresent('The Hello world app.')
-
- // Submit the form
- clickAndWait('css=input[type=submit]')
-
- // Check the error
- assertTextPresent('Oops, please enter your name!')
-
- // Type the name and submit
- type('css=input[type=text]', 'bob')
- clickAndWait('css=input[type=submit]')
-
- // Check the result
- assertTextPresent('Hello bob!')
- assertTextPresent('The Hello world app.')
-
- // Check the back link
- clickAndWait('link=Back to form')
-
- // Home page?
- assertTextNotPresent('Hello bob!')
- #{/selenium}
真是簡潔不少= =#
保存后我們運行一下
至此我們參照官網(wǎng)的第一個例子教程完成了helloworld,這個最簡單例子。
我們現(xiàn)在做的事情無非是照著官網(wǎng)step by step的把這個例子做完,并對Play有了初步的印象。
@
我想大部分我們學(xué)弟學(xué)妹在做完例子后,肯定是腦海浮現(xiàn)很多idea想去迫不及待的實現(xiàn)~其實大家都是這樣的,這是好事。
這里想插一件事情: 不知道你們有沒有選董群峰老師的課,我有幸聽了他的數(shù)據(jù)挖掘的課,更有幸的是聽到他在第二節(jié)課的即興演講: 記得不是非常準(zhǔn)確了,但是大致情形是,他給我們講數(shù)據(jù)挖掘的某算法之前,讓我們先想想應(yīng)該用什么樣的算法來解決那個問題,當(dāng)時我們很囧,因為我們剛初學(xué),怎么能想到那些大師發(fā)明的算法呢? 但是這個頭腦風(fēng)暴在一個非常蔥的MM大膽發(fā)言后居然就這么在整個課堂討論開了,最后短短半小時居然答案越來越接近了(當(dāng)然這和直接發(fā)明算法的大師還是區(qū)別很大的,因為每次發(fā)言后董老師會給出些提示,比如‘不對’ ‘接近了’ ‘更近了’之類的,幫我們剔除掉很多錯誤的路徑)。 隨后董老師展開演說,讓我們千萬不要忽視初識某個領(lǐng)域時腦海里想法,因為我們的思想還沒被以往沉積的知識體系固化,所以此時我們是創(chuàng)造力,創(chuàng)新力最旺盛的時期,任何想法都可能對此領(lǐng)域有重大的貢獻,不要退縮,不要自我否定,不要急功近利去啃書本。要勇于挑戰(zhàn)權(quán)威,勇于堅定自己的想法。 我可能有點演繹了,總之,我覺得這話說到我心坎了,尤其在我們中國,總覺得沒有個磚家叫獸之類的頭銜都不敢正視自己的想法,非要覺得自己的想法應(yīng)該是錯的,地位高的人說的就是對的。 這里我又想到Hibernate的創(chuàng)始人Gavin King(www.iteye.com/wiki/Celebrity/293-hibernate-founder-gavin-king)。他的經(jīng)歷可能正是這種需要自信,需要能堅持自己的想法,需要能挑戰(zhàn)權(quán)威的最好榜樣。 |
總結(jié):經(jīng)過了最簡單的例子,我們看到了Play的部分特點:
- 寫完代碼后不用重啟應(yīng)用服務(wù)器重新部署包就可以直接運行改變后的代碼。
- Play的模板引擎優(yōu)化了UI開發(fā)的流程,模板間可以通過繼承實現(xiàn)復(fù)用,另外,模板中的表達式和Tag也非常好用。
- 測試框架界面良好,功能齊全,對selenium的腳本也進行了優(yōu)化(可以用Play的模板寫)。
- 控制器的接口簡單,通過render()方法傳遞參數(shù)即可將數(shù)據(jù)模型傳至視圖層。
目標(biāo)二:Demo總結(jié)及閱讀Play源碼
以上特點都是我們實際跑例子直觀看到的一些東西,下面我們深入一點,通過Debug demo的方式深入Play的源碼看看Play是具體如何工作的:
@
手把手教新手學(xué)弟學(xué)妹讀代碼:以上總結(jié)都是我們通過run demo直觀看到的Play特性,本文除了是自己學(xué)習(xí)play的總結(jié),更希望能引導(dǎo)學(xué)弟學(xué)妹用正確的方式讀開源項目的源碼。 這里先說一下我覺得初涉編程,閱讀源碼容易出現(xiàn)的錯誤: 1:從官網(wǎng)下了代碼包后直接解壓后逐個文件掃描,比較像讀小說,按頁翻,這樣看肯定是稀里糊涂,看不出什么門道來。 2:要深入到一定層次(比如:能靈活使用框架,能做出復(fù)雜的引用,官方文檔也讀了不少)才敢去讀源代碼,總覺得斤兩不夠的時候讀了也沒啥用。 我個人認(rèn)為,讀源碼也應(yīng)該是漸進式的,從最簡單開始,即從helloworld開始。 讀代碼要通過debug demo的方式去進行,善用step into和step over兩種步進方式(什么,你分不清step into和step over的區(qū)別?。。??趕緊去谷哥之) 此外,由于剛開始看,肯定會有很多不明白的(因為框架的運行是個復(fù)雜的過程,其中很多工作我們可能還沒有接觸到,也就不會有體會,當(dāng)然看不懂)。因此我們應(yīng)該盡量挑看的懂的地方看,要帶著問題去看: 比如本例,我們可以想想: 數(shù)據(jù)模型是怎么傳遞的視圖的 加參數(shù)校驗是怎么實際工作的。 校驗失敗的錯誤信息時何時寫入flash scope并伴隨redirect傳遞到視圖層。 |
因為需要Debug,因此需要IDE的支持,這里我們使用Eclipse。
Play提供了命令,可以直接將Play的項目轉(zhuǎn)換成eclipse項目。
進入放置helloaworld的目錄,使用命令
play eclipsify myApp
即可完成轉(zhuǎn)換。
然后直接用Eclipse導(dǎo)入該項目即可
在Eclipse的package Explorer中,我們可以看到項目里多了一個eclipse目錄
eclipse導(dǎo)入項目.png
此目錄里的三個文件:
- Connect JPDA to myFirstApp.launch :用來Debug程序,右鍵菜單 Debug As..Connect JPDA to myFirstApp[需要先run myFirstApp.launch]
- myFirstApp.launch:用來run app,效果和play run一樣,右鍵菜單 Run As..myFirstApp
- Test myFirstApp.launch :用來test,效果和play test一樣
注※ myFirstApp是項目名,如果參照例子做,項目名應(yīng)該是helloworld
開始Debug閱讀源碼
此處我們先run myFirstApp.launch,然后Debug As..Connect JPDA to myFirstApp.
在Application的index()方法里的render()方法設(shè)上斷點。
打開瀏覽器訪問主頁,程序執(zhí)行到斷點,轉(zhuǎn)到eclipse,按F5進入render()方法,此時,由于class文件沒有和source文件關(guān)聯(lián),所以看不到源碼。
根據(jù)提示點擊"Change Attach source",選擇Play1.02目錄下的xxx\play-1.0.2\framework\src即可完成關(guān)聯(lián),稍等即可看到源碼。
按F6繼續(xù)往下走(F5跳進方法內(nèi),F(xiàn)6在方法內(nèi)步進)
我們現(xiàn)在在Controller類里面:
先看看Controller類:
這個類沒有抽象方法,但是有abstract關(guān)鍵字,是個抽象類。
這個類所有方法都是靜態(tài)的protected方法,成員變量也都是靜態(tài)的,除了_currentReverse這個ThreadLocal變量外,其他也都是protected的。
因此,在\app\controllers包下要使用Controller的靜態(tài)方法,必須通過繼承。
然而,從作為一個父類考慮,此類沒有成員變量和方法,子類繼承后沒有得到父類的任何成員,在面向?qū)ο筮@一角度觀察和實現(xiàn)一個空接口的功能相似,即標(biāo)明這類是個controller。
因此我的理解是,這種設(shè)計,當(dāng)我們在\app\controllers下面寫一個Application并繼承controller后,作用就是標(biāo)明 Application是個controller,并且Application作為一個代理執(zhí)行controller類的靜態(tài)方法。
同時,Application要實現(xiàn)controller的核心功能,必須調(diào)用controller的靜態(tài)方法,因此,調(diào)用此方法的方法也必須是靜態(tài)的。從而Application也沒有被實例化的必要,所以可以證明,Play中的控制器是沒有實例存在于容易中的。
回到render()方法,這個方法的參數(shù)是個類型為object的可變參數(shù)列表,
Java代碼:
- protected static void render(Object... args) {
- ...
- }
可見render這個接口是個吞吐量巨大的視圖層入口,為模板傳遞數(shù)據(jù)。
這個方法的主要功能取得到要render的模板名(templateName)。
此時的邏輯很簡單,由于參數(shù)為空,走入else,然后拼接訪問index的路徑文件名Application/index.html(用Debug的方式閱讀代碼的另一個好處,即一些與我們關(guān)注點不大的邏輯直接走過后用watch查看結(jié)果,能加快我們隊代碼的理解)
Java代碼:
- String templateName = null;
- if (args.length > 0 && args[0] instanceof String && LocalVariablesNamesTracer.getAllLocalVariableNames(args[0]).isEmpty()) {
- templateName = args[0].toString();
- } else {
- templateName = Http.Request.current().action.replace(".", "/") + "." + (Http.Request.current().format == null ? "html" : Http.Request.current().format);
- }
我們繼續(xù)往下走。先不管[if(templateName.startsWith("@"))]處的邏輯,因為暫時沒遇到過這個case,等以后再看不遲。
走到 renderTemplate(templateName, args); 我們F5進去看看
這個方法將模板名以及render的可變參數(shù)列表傳遞進來,再加上方法名,我們猜測這個方法的作用是根據(jù)模板名找到模板,然后把可變參數(shù)列表里的對象傳到模板上。
看看實現(xiàn):
Java代碼:
- protected static void renderTemplate(String templateName, Object... args) {
- // Template datas
- Scope.RenderArgs templateBinding = Scope.RenderArgs.current();
- for (Object o : args) {
- List<String> names = LocalVariablesNamesTracer.getAllLocalVariableNames(o);
- for (String name : names) {
- templateBinding.put(name, o);
- }
- }
- templateBinding.put("session", Scope.Session.current());
- templateBinding.put("request", Http.Request.current());
- templateBinding.put("flash", Scope.Flash.current());
- templateBinding.put("params", Scope.Params.current());
- try {
- templateBinding.put("errors", Validation.errors());
- } catch (Exception ex) {
- throw new UnexpectedException(ex);
- }
- try {
- Template template = TemplateLoader.load(templateName);
- throw new RenderTemplate(template, templateBinding.data);
- } catch (TemplateNotFoundException ex) {
- if(ex.isSourceAvailable()) {
- throw ex;
- }
- StackTraceElement element = PlayException.getInterestingStrackTraceElement(ex);
- if (element != null) {
- throw new TemplateNotFoundException(templateName, Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber());
- } else {
- throw ex;
- }
- }
- }
先得到Scope.RenderArgs對象,還記得我們在之前加參數(shù)校驗時,按照官網(wǎng)說法,如果校驗失敗,錯誤信息會放在Scope.flash里。
此處又用到了Scope的內(nèi)部類RenderArgs的實例儲存Render參數(shù)。
Scope在J2EE里通常是指生命周期的意思,因此Scope中保存的是各種狀態(tài):
根據(jù)代碼可以清晰地看出,此處Scope.RenderArgs里存放了各種狀態(tài)(session,request,flash,傳遞進來的參數(shù)和出錯信息)
Template template = TemplateLoader.load(templateName);
這個方法把模板實例load進來。模板的load過程我們后面單獨開一節(jié)說,先關(guān)注核心部分。
原文鏈接:http://djb4ke.iteye.com/blog/662240
【編輯推薦】