Play! Framework 系列(二):play 的项目结构
在?Play! Framework 系列(一)中我們初步了解了一下 Play! 的各種特性以及優(yōu)勢,那么從現(xiàn)在開始我們將正式接觸 Play!。本文將介紹一下 Play! 的整體結(jié)構(gòu),然后通過一個(gè)非常簡單的例子來闡述各個(gè)結(jié)構(gòu)之間的關(guān)系以及如何利用 Play! 約定的結(jié)構(gòu)去合理地組織我們的業(yè)務(wù)邏輯。
結(jié)構(gòu)概覽
上圖為基于 Play! 而創(chuàng)建的一個(gè)簡單的 Web 應(yīng)用,在上一篇文章中我們說過 Play! 是「ROR」風(fēng)格的框架,通過上圖我們也可以看到 Play! 是典型的 MVC 架構(gòu)框架,另外 Play! 也采用「約定優(yōu)于配置」,我們只需要按照其約定的結(jié)構(gòu)去組織我們的代碼就可以很輕松地實(shí)現(xiàn)一個(gè) Web 應(yīng)用,那么接下來我們就去了解一下 Play! 中各個(gè)結(jié)構(gòu)的特點(diǎn)以及功能吧。
業(yè)務(wù)描述
我們將通過實(shí)現(xiàn)一個(gè)小應(yīng)用的方式去了解 Play! 的基本結(jié)構(gòu),這樣會更加清晰一些。需求描述:
- 實(shí)現(xiàn)一個(gè)簡單的公司員工信息列表。
可以看到,我們將要實(shí)現(xiàn)的 Web 應(yīng)用非常簡單,接下來我們就通過這個(gè)小小的需求去把玩一下 Play! 吧。
app
1 2 3 4 | app └ controllers └ models └ views |
目錄 app 排在結(jié)構(gòu)圖中的最上面,因?yàn)槭前凑帐鬃帜概帕械?#xff0c;所以它理應(yīng)在最前面。當(dāng)然,它在我們整個(gè) Play 應(yīng)用中也是非常重要的,幾乎我們所有的業(yè)務(wù)代碼都包含在該目錄下面,既然它如此重要,排在最前面也無可厚非。在 app 下三個(gè)子目錄,分別是:controllers、models 以及 views。
我們也可以在 app 目錄下增加一些目錄,比如,我們需要利用 Play! 的 Filter (后面會介紹)來實(shí)現(xiàn)一些需求,那么我們可以在該目錄下新增一個(gè) filters 目錄,專門用來管理 Filter 的業(yè)務(wù)邏輯。例如:
1 2 3 4 5 | app └ controllers └ models └ views └ filters |
接下來我們將詳細(xì)介紹該目錄下的三個(gè)核心結(jié)構(gòu):controllers、models 以及 views。
models
在 MVC 結(jié)構(gòu)的 Web 應(yīng)用中,M 對應(yīng)的就是 Model,在 models 下,我們實(shí)現(xiàn)數(shù)據(jù)訪問的一些邏輯,一般來說,數(shù)據(jù)庫中的一個(gè)表就對應(yīng)一個(gè) model 類。例如:
我們將要顯示「員工」列表,這里我們需要數(shù)據(jù)庫中的「員工表」,那么在 models 下,我們創(chuàng)建一個(gè)表示員工信息的 model:
1 2 3 4 5 6 | caseclassEmployee ( id: Long, name: String, sex: String, position: String ) |
一般情況下,我們也需要在 models 下實(shí)現(xiàn)操作數(shù)據(jù)庫的邏輯,但是當(dāng)業(yè)務(wù)比較復(fù)雜的時(shí)候,整個(gè)文件看上去會特別凌亂,并且后期也不好維護(hù),所以這里我們引入 services,我們將在 services 下實(shí)現(xiàn)所有與數(shù)據(jù)庫打交道的邏輯,而 models 下,我們只需要它定義相應(yīng)的 model 類就可以了。
1 2 3 4 5 | app └ controllers └ models └ views └ services |
services
我們將在 services 下新建一個(gè) EmployeeService 去實(shí)現(xiàn)員工信息的查詢操作:
注:本文不涉及數(shù)據(jù)庫,所以在這里我們把數(shù)據(jù)都寫死,數(shù)據(jù)庫連接后面的文章會詳細(xì)講解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | classEmployeeService { val jilen = Employee( id = 1, name = "Jilen", sex = "男", position = "全干工程師" ) val yison = Employee( id = 2, name = "Yison", sex = "女", position = "程序員鼓勵師" ) def getEmployees: Seq[Employee] = Seq(jilen, yison) } |
views
View 對應(yīng)的就是 MVC 結(jié)構(gòu)中的 V,在該結(jié)構(gòu)下,我們實(shí)現(xiàn)程序中的視圖,也就是利用 Play! 的模板去實(shí)現(xiàn) html 頁面,在 view 中,我們一般只做數(shù)據(jù)的渲染,很少實(shí)現(xiàn)復(fù)雜的邏輯。為了呈現(xiàn)員工列表,我們在 views 下創(chuàng)建一個(gè)名為 employeeList.scala.html 的文件,在該文件下,我們主要實(shí)現(xiàn)數(shù)據(jù)的渲染,這里只寫一些主要的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @(employees: Seq[Employee]) <table class="employee-list"> <tr> <th>編號</th> <th>姓名</th> <th>性別</th> <th>職位</th> </tr> @for(e <- employees){ <tr> <td>@e.id</td> <td>@e.name</td> <td>@e.sex</td> <td>@e.position</td> </tr> } </table> |
controllers
前面我們創(chuàng)建好了 model、servic 以及 view,那如何將 model、service 中的數(shù)據(jù)渲染到 view 中去呢?這個(gè)時(shí)候就需要 controller 了,Controller 對應(yīng)于 MVC 中的C,在 controllers 下面,我們需要實(shí)現(xiàn)一些列的 action,通過這些 action 來將整個(gè) Web 程序的數(shù)據(jù)聯(lián)系在一起。為了將前面創(chuàng)建的 model、service 以及 view 聯(lián)系起來,我們在 controllers 下創(chuàng)建一個(gè) EmployeeController:
1 2 3 4 5 6 7 8 9 10 11 12 | classEmployeeController@Inject() ( cc: ControllerComponents ) extends AbstractController(cc) { val employeeSerivce = newEmployeeSerivce def employeeList = Action { implicit request: Request[AnyContent] => val employees = employeeSerivce.getEmployees() Ok(views.html.employeeList(employees)) } } |
這里我們簡單介紹一下 Play 中的?Action,Play 中的「Action」實(shí)際上是一個(gè)「特質(zhì)(trait)」,我們上面的代碼實(shí)現(xiàn)了一個(gè)「Action」,這里實(shí)際上是使用了?object Action,然后「object Action」中的「apply」方法會返回一個(gè) Action:
1 2 3 4 | // object Action 的 apply 方法 finaldef apply(block: ?Result): Action[AnyContent] |
conf
1 2 3 4 | conf └ application.conf └ routes |
在 conf 下面,我們主要放置整個(gè)項(xiàng)目的配置文件和路由文件。
application.conf
該文件將配置 Play! 應(yīng)用的一系列信息,比如 secret key,數(shù)據(jù)庫信息等,由于我們的應(yīng)用比較簡單,所以這里不需要配置該項(xiàng),在后面的文章中,我們將專門介紹如何管理 application.conf。
routes
前面我們實(shí)現(xiàn)了 model、service、controller 以及 view,那我們?nèi)绾瓮ㄟ^瀏覽器去訪問我們的應(yīng)用呢,這里就需要使用「路由」了,應(yīng)用程序的所有路由都將在 routes 中實(shí)現(xiàn),這些路由就是應(yīng)用程序的入口。例如:
要想訪問我們之前實(shí)現(xiàn)的「員工列表」,我們就需要在 routes 中指定相應(yīng)的路由:
1 2 | GET /employee/employee-list controllers.EmployeeController.employeeList |
指定好路由之后,當(dāng)我們在瀏覽器中輸入?http://localhost:9000/employee/employee-list?的時(shí)候,就能訪問到「員工列表」頁面了。
關(guān)于 routes,我們在 route 文件中只是寫了這么一段去指定,當(dāng)編譯完成之后,我們在?target/scala-2.12/routes/main/router/?下可以看到一個(gè)名為 Route.scala 的文件,在文件的末尾可以看到:
1 2 3 4 5 6 7 8 | def routes: PartialFunction[RequestHeader, Handler] = { case controllers_EmployeeController_employeeList0_route(params) => call { controllers_EmployeeController_employeeList0_invoker.call(EmployeeController_0.employeeList) } } |
可見其實(shí) routes 在 play! 中的實(shí)現(xiàn)是一個(gè)方法,它是一個(gè)「偏函數(shù)」當(dāng)某個(gè)請求被匹配到了就調(diào)用相應(yīng)的方法,如果沒有匹配到則報(bào)錯(cuò),所以我們也可以自己實(shí)現(xiàn)某個(gè)路由,而不用 play! 的這種方式,當(dāng)然用 play! 約定好會更加清晰和簡單。
在介紹完 routes 之后,我們有必要知道一下當(dāng)我們在瀏覽器中輸入某個(gè)鏈接的時(shí)候,play! 的各個(gè)模塊之間是如何調(diào)用的,如下圖:
當(dāng)我們訪問某個(gè)鏈接的時(shí)候,該鏈接就是對應(yīng)的一個(gè)路由,該路由會去匹配某個(gè) Controller 中的 Action,接下來 Action 要去調(diào)用所依賴的 Service 中的方法,這些方法將數(shù)據(jù)獲取到傳遞給 Action,然后 Action 將這些數(shù)據(jù)送給 View,View 就會將我們所需要的頁面渲染出來了。這個(gè)流程如圖中的實(shí)線所示,同時(shí) Controller 也會依賴 Model,有時(shí)候 View 也會去依賴 Model 以及 Service。
build.sbt
該文件用來定義我們項(xiàng)目的一些基本信息以及項(xiàng)目所需要的一些依賴的信息,比如項(xiàng)目的名稱、所屬組織、版本信息、scala 的版本以及一些依賴的定義等等,在我們的應(yīng)用中,build.sbt 可以這樣定義:
1 2 3 4 5 6 7 8 9 10 11 12 | name := "HelloWorld" organization := "com.shawdubie" version := "1.0-SNAPSHOT" lazyval root = (project in file(".")).enablePlugins(PlayScala) scalaVersion := "2.12.2" libraryDependencies += guice libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "3.1.0" % Test |
build.sbt 文件在 sbt 啟動的時(shí)候就會被讀取,然后 sbt 就會去加載我們在里面定義的一些信息,比如我們聲明的一些依賴。build.sbt 可以包含許多信息,關(guān)于更詳細(xì)的我們后面再討論,這里只需要知道她。
project
1 2 3 4 | project └ build.properties └ plugins.sbt |
該目錄下主要放置 sbt 構(gòu)建之后的文件,在構(gòu)建之前,該目錄下一般就只有上面所列的兩個(gè)文件。
build.properties
這里定義了該項(xiàng)目所依賴的 sbt 的版本信息,例如該項(xiàng)目中 sbt 的版本就可以這樣聲明:
1 | sbt.version=0.13.15 |
plugins.sbt
在該文件下我們聲明該項(xiàng)目所依賴的一些插件,比如我們使用了 play sbt 插件:
1 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3") |
結(jié)語
本文通過一個(gè)例子讓我們大致了解了 Play! 的基本結(jié)構(gòu),文中有一些一筆帶過的內(nèi)容我們將在后面的文章中詳細(xì)介紹,這里只需要知道就可以了。本文的例子請戳?源碼鏈接
?
本文轉(zhuǎn)載自:https://scala.cool/2017/09/play2/
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Play! Framework 系列(二):play 的项目结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Play! Framework 系列(一
- 下一篇: Play! Framework 系列(三