本文由 Shaw 发表在 ScalaCool 团队博客。html
在 Play! Framework 系列(一)中咱们初步了解了一下 Play! 的各类特性以及优点,那么从如今开始咱们将正式接触 Play!。本文将介绍一下 Play! 的总体结构,而后经过一个很是简单的例子来阐述各个结构之间的关系以及如何利用 Play! 约定的结构去合理地组织咱们的业务逻辑。git
上图为基于 Play! 而建立的一个简单的 Web 应用,在上一篇文章中咱们说过 Play! 是「ROR」风格的框架,经过上图咱们也能够看到 Play! 是典型的 MVC 架构框架,另外 Play! 也采用 「约定因为配置」,咱们只须要按照其约定的结构去组织咱们的代码就能够很轻松地实现一个 Web 应用,那么接下来咱们就去了解一下 Play! 中各个结构的特色以及功能吧。程序员
咱们将经过实现一个小应用的方式去了解 Play! 的基本结构,这样会更加清晰一些。需求描述:github
能够看到,咱们将要实现的 Web 应用很是简单,接下来咱们就经过这个小小的需求去把玩一下 Play! 吧。数据库
app
└ controllers
└ models
└ views复制代码
目录 app 排在结构图中的最上面,由于是按照首字母排列的,因此它理应在最前面。固然,它在咱们整个 Play 应用中也是很是重要的,几乎咱们全部的业务代码都包含在该目录下面,既然它如此重要,排在最前面也无可厚非。在 app 下三个子目录,分别是:controllers、models 以及 views。api
咱们也能够在 app 目录下增长一些目录,好比,咱们须要利用 Play! 的 Filter (后面会介绍)来实现一些需求,那么咱们能够在该目录下新增一个 filters 目录,专门用来管理 Filter 的业务逻辑。例如:浏览器
app
└ controllers
└ models
└ views
└ filters复制代码
接下来咱们将详细介绍该目录下的三个核心结构:controllers、models 以及 views。bash
在 MVC 结构的 Web 应用中,M 对应的就是 Model,在 models 下,咱们实现数据访问的一些逻辑,通常来讲,数据库中的一个表就对应一个 model 类。例如:架构
咱们将要显示「员工」列表,这里咱们须要数据库中的「员工表」,那么在 models 下,咱们建立一个表示员工信息的 model:mvc
case class Employee ( id: Long, name: String, sex: String, position: String )复制代码
通常状况下,咱们也须要在 models 下实现操做数据库的逻辑,可是当业务比较复杂的时候,整个文件看上去会特别凌乱,而且后期也很差维护,因此这里咱们引入 services,咱们将在 services 下实现全部与数据库打交道的逻辑,而 models 下,咱们只须要它定义相应的 model 类就能够了。
app
└ controllers
└ models
└ views
└ services复制代码
咱们将在 services 下新建一个 EmployeeService 去实现员工信息的查询操做:
注:本文不涉及数据库,因此在这里咱们把数据都写死,数据库链接后面的文章会详细讲解。
class EmployeeService {
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)
}复制代码
View 对应的就是 MVC 结构中的 V,在该结构下,咱们实现程序中的视图,也就是利用 Play! 的模板去实现 html 页面,在 view 中,咱们通常只作数据的渲染,不多实现复杂的逻辑。为了呈现员工列表,咱们在 views 下建立一个名为 employeeList.scala.html 的文件,在该文件下,咱们主要实现数据的渲染,这里只写一些主要的代码:
@(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>复制代码
前面咱们建立好了 model、servic 以及 view,那如何将 model、service 中的数据渲染到 view 中去呢?这个时候就须要 controller 了,Controller 对应于 MVC 中的 的 C,在 controllers 下面,咱们须要实现一些列的 action,经过这些 action 来将整个 Web 程序的数据联系在一块儿。为了将前面建立的 model、service 以及 view 联系起来,咱们在 controllers 下建立一个 EmployeeController:
class EmployeeController @Inject() ( cc: ControllerComponents ) extends AbstractController(cc) {
val employeeSerivce = new EmployeeSerivce
def employeeList = Action { implicit request: Request[AnyContent] =>
val employees = employeeSerivce.getEmployees()
Ok(views.html.employeeList(employees))
}
}复制代码
这里咱们简单介绍一下 Play 中的 Action,Play 中的 「Action」 其实是一个「特质(trait)」,咱们上面的代码实现了一个 「Action」, 这里其实是使用了 object Action,而后 「object Action」 中的 「apply」 方法会返回一个 Action:
// object Action 的 apply 方法
final def apply(block: ⇒ Result): Action[AnyContent]复制代码
conf
└ application.conf
└ routes复制代码
在 conf 下面,咱们主要放置整个项目的配置文件和路由文件。
该文件将配置 Play! 应用的一系列信息,好比 secret key,数据库信息等,因为咱们的应用比较简单,因此这里不须要配置该项,在后面的文章中,咱们将专门介绍如何管理 application.conf。
前面咱们实现了 model、service、controller 以及 view,那咱们如何经过浏览器去访问咱们的应用呢,这里就须要使用「路由」了,应用程序的全部路由都将在 routes 中实现,这些路由就是应用程序的入口。例如:
要想访问咱们以前实现的「员工列表」,咱们就须要在 routes 中指定相应的路由:
GET /employee/employee-list controllers.EmployeeController.employeeList复制代码
指定好路由以后,当咱们在浏览器中输入 http://localhost:9000/employee/employee-list
的时候,就能访问到「员工列表」页面了。
关于 routes,咱们在 route 文件中只是写了这么一段去指定,当编译完成以后,咱们在 target/scala-2.12/routes/main/router/
下能够看到一个名为 Route.scala 的文件,在文件的末尾能够看到:
def routes: PartialFunction[RequestHeader, Handler] = {
case controllers_EmployeeController_employeeList0_route(params) =>
call {
controllers_EmployeeController_employeeList0_invoker.call(EmployeeController_0.employeeList)
}
}复制代码
可见其实 routes 在 play! 中的实现是一个方法,它是一个「偏函数」当某个请求被匹配到了就调用相应的方法,若是没有匹配到则报错,因此咱们也能够本身实现某个路由,而不用 play! 的这种方式,固然用 play! 约定好会更加清晰和简单。
在介绍完 routes 以后,咱们有必要知道一下当咱们在浏览器中输入某个连接的时候,play! 的各个模块之间是如何调用的,以下图:
当咱们访问某个连接的时候,该连接就是对应的一个路由,该路由会去匹配某个 Controller 中的 Action,接下来 Action 要去调用所依赖的 Service 中的方法,这些方法将数据获取到传递给 Action,而后 Action 将这些数据送给 View,View 就会将咱们所须要的页面渲染出来了。这个流程如图中的实线所示,同时 Controller 也会依赖 Model,有时候 View 也会去依赖 Model 以及 Service。
该文件用来定义咱们项目的一些基本信息以及项目所须要的一些依赖的信息,好比项目的名称、所属组织、版本信息、scala 的版本以及一些依赖的定义等等,在咱们的应用中,build.sbt 能够这样定义:
name := "HelloWorld"
organization := "com.shawdubie"
version := "1.0-SNAPSHOT"
lazy val 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 启动的时候就会被读取,而后 sbt 就会去加载咱们在里面定义的一些信息,好比咱们声明的一些依赖。build.sbt 能够包含许多信息,关于更详细的咱们后面再讨论,这里只须要知道她。
project
└ build.properties
└ plugins.sbt复制代码
该目录下主要放置 sbt 构建以后的文件,在构建以前,该目录下通常就只有上面所列的两个文件。
这里定义了该项目所依赖的 sbt 的版本信息,例如该项目中 sbt 的版本就能够这样声明:
sbt.version=0.13.15复制代码
在该文件下咱们声明该项目所依赖的一些插件,好比咱们使用了 play sbt 插件:
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.3")复制代码
本文经过一个例子让咱们大体了解了 Play! 的基本结构,文中有一些一笔带过的内容咱们将在后面的文章中详细介绍,这里只须要知道就能够了。本文的例子请戳 源码连接