1. 概览
在本文中将介绍 Scala 的 Play Web 开发框架。我们将会学习如何创建一个 Play 项目,使用开发工具生成我们的第一个项目以及实现自定义的功能,另外还将体验一下 Play 框架的测试能力。
对于 Java 开发者来说,也可以看这篇文章 Introduction To Play In Java。
2. 项目搭建
在开始之前,我们需要安装 sbt 命令行工具 (至少是 JDK 8 及以上),在本文中我们将使用 sbt 1.6.2 来安装 Play Framework 2.8.16
3. 命令行工具
Play 框架官方文档提到 sbt 是一个强大的控制台和构建工具,我们可以从使用 sbt
工具生成一个空白的 Play 框架的项目开始。
sbt new playframework/play-scala-seed.g8 复制代码
在依赖信息加载完成后,该工具将提示并要求我们输入新项目的名称和组织信息
This template generates a Play Scala project name [play-scala-seed]: 复制代码
我们给这个项目命名为 baeldung-play-framework.。组织的名称将会作为项目中包的名称,Scala 的包名的命名规则和 Java 的包名命名规则一样,因此我可以可以给包命名为 baeldung.com
现在,我们可以进入到 baeldung-play-framework 项目文件夹中并启动该项目
cd baeldung-play-framework sbt run 复制代码
这是我们第一次启动项目,可能会花点时间在构建和编译上。
完成后我们可以通过浏览器进入 http://localhost:9000/ 就可以看到默认的欢迎页面了
到目前为止我们已经通过 Play 框架创建了一个正在运行的 HTTP 服务器,并且没有书写一行代码就完成了。
4. 项目结构
现在,可以使用 IntelliJ IDE 打开项目并查看项目的目录结构
在项目目录中,有四个文件夹是由 sbt 模板创建的,分别是 app/controllers, app/views, conf 和 public。
- controllers 目录用来保存 Scala 代码
- views 目录用来保存 HTML 模板
- conf 保存着路由配置既请求的 URL 地址和类以及函数的映射关系
- public 目录保存着 Play 框架服务器的一些静态内容
baedung-play-framework$ tree -L 2 . ├── app │ ├── controllers │ └── views ├── build.sbt ├── build.sc ├── conf │ ├── application.conf │ ├── logback.xml │ ├── messages │ └── routes ├── logs │ └── application.log ├── project │ ├── build.properties │ ├── plugins.sbt │ ├── project │ └── target ├── public │ ├── images │ ├── javascripts │ └── stylesheets ├── target │ ├── global-logging │ ├── scala-2.13 │ ├── streams │ ├── task-temp-directory │ └── web └── test └── controllers 20 directories, 9 files 复制代码
这就是目前我们需要了解的关于项目的目录结构。
5. 第一次变更
Play 框架为我们提供了一个“点击刷新工作流”。意味着我们可以通过刷新浏览器就可以查看更改后的内容,而无需重新启动服务器。
首先我们在 app/views 文件夹下创建一个新文件并命名为 firstexample.scala.html,
打开这个文件并输入以下代码:
@() @main("Welcome to Introductio to Play Framework"){ <h1>Welcome to Introduction to Play Framework</h1> } 复制代码
除了修改 HTML 文件,我们还必须修改 Scala 代码。打开 app/controllers/HomeController.scalas 文件并将:Ok(views.html.index())
更改为 Ok(viewers.html.firstexample())
def index() = Action { implicit request: Request[AnyContent] => Ok(views.html.index()) } 复制代码
def index() = Action { implicit request: Request[AnyContent] => Ok(views.html.firstexample()) } 复制代码
查看仍在运行的 Play 服务器的输出时,没有看到任何新内容。似乎更改未生效。
但是,当我们点击浏览器中的 “刷新” 按钮时,我们将在浏览器窗口中看到以下内容:
6. 如何定义一个新的请求
在前面的示例中,我们对代码进行了一些更改,并看到了更改后的结果。现在,让我们看看 Play 框架项目的内部结构,了解它是如何工作的,以及我们还可以做什么。
当 Play 项目服务器接收到请求时,它首先会检查 conf/routes 文件,以确定哪个 Controller 控制器和方法将处理该请求。在 Controller 控制器内部定义并在路由文件中使用的方法称为 Action。
我们想在 HomeController 控制器中定义一个新的 Action 以及路由,这个 Action 将会从 URL 地址中接收到两个参数并且打印出这两个数的和。简单来说我们将从 URL 中读取两个数并且在页面中展示这两个数的和
为了实现这个功能,我们需要在 HomeController.scala 控制器中添加新的方法,这个方法接收两个参数,计算它们的和并传递到视图模板中渲染。
def printSum(first: Long, second: Long) = Action { implicit request: Request[AnyContent] => val sum = first + second Ok(views.html.index(sum)) } 复制代码
现在打开视图模板 index.scala.html,在模板顶部添加 sum 参数并在内容中使用这个参数
@(sum: Long) @main("Add two numbers") { <h1>The sum is @sum</h1> } 复制代码
我们刚刚定义了一个生成页面的函数。视图文件的第一行描述函数参数。其他行是生成输出的代码。
该 sum 参数在 HomeController.scala 中计算并传递给 Ok 函数,该函数返回状态代码为 200 内容为 OK
最后我们需要打开 conf/routes 文件并添加一个新的路径和 action
# add two numbers GET /sum/:first/:second controllers.HomeController.printSum(first: Long, second: Long) 复制代码
该路由包含了三个部分,第一个是 HTTP 的请求方式,接着我们定义了路径以及参数,这里我们使用两个变量 first 和 second 来计算 sum 参数。
最后我们通过指定 Controller 以及处理请求的 action,需要注意的是我们在路径中使用的参数正是函数中用到的参数。
在浏览器中打开如下地址 http://localhost:9000/sum/5/15 就可以看到这个页面
7. 编写测试用例
最后,我们来看看有 sbt 命令行工具在创建 Play 框架项目的时候生成的测试用例文件夹。
从 tests/controllers 目录下打开 HomeControllerSpec 文件时,我们会看到 ScalaTest 的一些规范。
为了使测试用例更完整,我们需要为我们路由编写测试用例。定义一个名为 “render a page that prints the sum of two numbers” 的新测试用例,该测试用例会调用 /sum 路由并带有两个路径参数
"render a page that prints the sum of two numbers" in { val request = FakeRequest(GET, "/sum/10/20") val sumOfNumbers = route(app, request).get } 复制代码
调用完之后还需要添加断言来判断结果,在这个测试用例中我们期望看到 “The sum is 30” 在 HTML 页面上渲染出来。
// 断言内容 status(sumOfNumbers) mustBe OK contentType(sumOfNumbers) mustBe Some("text/html") contentAsString(sumOfNumbers) must include("The sum is 30") 复制代码
我们可以通过 sb test 命令来执行测试,执行完成后就可以看到我们的测试用执行通过
8. 总结
在本文中,我们使用 Play Framework 的命令行工具创建了一个简单的网站,添加了一个新的视图模板,并使用参数化模板定义了一条新路由。最后,我们查看了自动生成的测试用例并实现了我们创建的功能的测试用例并通过测试。
该项目的源码可以在 GitHub 上获得。