Play framework 抛弃了传统的 Java Web 框架模式,选择了「响应式」( Reactive ) 编程。Play 框架具备以下特点:
- 响应式 ( Resposive ):快速响应用户行为。
- 可伸缩 ( Scalable ):负载方面,Play能够很好的水平拓展。
- 事件驱动 ( Event-driven ):基于事件 ( Action ) 模型实现 HTTP Server。
Play 是快速开发的利器,遵从ROR ( Ruby on Rails ) 的开发风格,支持使用 Scala 和 Java 开发,适用于小型的个人项目。
👉想要了解更多特性。 | Play2 (Java/Scala)技術框架 (wisdomfish.org)
本文演示了以下内容的配置和安装:SBT,MongoDB,Play2。
1. 开发环境需求
- 要求 jdk 的版本在1.8+。
- 安装 sbt。
- Intellij IDEA 2018+ 版本需要安装 scala 插件。
- MongoDB 版本在 3.6 及以上。
- 项目 sdk 不要超过 2.13 版本。(有些插件没有及时更新)
2. 开发语言及版本选择
| 工具 | 版本 |
|---|---|
| 开发环境 | Windows 10 |
| jdk | 1.8.0_241 |
| sdk(项目的) | 2.12.1 |
| MongoDB | 3.6.x |
| sbt | 1.3.10 |
| Play framework | 2.8.x |
| 开发语言 | scala |
play scala 目前没有对 Mysql 提供更好的支持,因此要意味着要基于原生的 jdbc 组件开发,因此本项目转用 MongDB 数据库进行开发。
3. Play 项目的基本格局
app → 项目源码目录
└ controllers → 控制层
└ models → 业务层
└ views → 模板层
build.sbt → 项目构建脚本
conf → 配置文件 (on classpath)
└ application.conf → 主配置文件
└ routes → 路由配置
public → 静态文件目录
└ stylesheets → CSS 文件
└ javascripts → Javascript 文件
└ images → Image 文件
project → sbt 配置文件
└ build.properties → 配置sbt版本
└ plugins.sbt → 配置sbt插件
lib → 第三方jar包依赖
logs → 日志目录
└ application.log → 默认日志文件
target → 构建时动态生成目录
└ resolution-cache → 依赖信息
└ scala-2.xx
└ api → 生成的API文档
└ classes → 编译后的class文件
└ routes → 从routes文件生成的源码
└ twirl → 从模板文件生成的源码
└ universal → 项目打包目录
test → 测试目录
.gitignore → git忽略列表配置
注意,sbt 不会主动编译第三方依赖 lib 内的 jar 包,如果需要使用内部开发的 jar 包,则需要上网上查询 assembly 命令相关用法。
4. sbt 安装并配置国内镜像
自行在 /conf 目录下创建一个 repo.properties 文件,更改镜像地址。有些论坛还会带上 maven-central 行,这会导致 sbt 仍然优先从 maven 仓库下载文件,所以需要将这行去除。sbt 在寻找依赖时,会依次按配置顺序从镜像中下载依赖。
[repositories]
local
huaweicloud-maven: https://repo.huaweicloud.com/repository/maven/
aliyun: https://maven.aliyun.com/nexus/content/groups/public/
mvn: https://repo1.maven.org/maven2/
sbt-plugin-repo: https://repo.scala-sbt.org/scalasbt/sbt-plugin-releases, [organization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]
Windows 环境下则需要在 $SBT_PATH/sbt/conf/sbtconfig.txt 进行额外配置,指定 sbt 的下载路径。其中 C:/sbt 需要由实际的安装路径进行替换,然后通过 -Dsbt.repository.config 选项将刚才的镜像配置包括进来:
# sbt configuration file for Windows
# Set the java args
#-mem 1024 was added in sbt.bat as default
#-Xms1024m
#-Xmx1024m
#-Xss4M
#-XX:ReservedCodeCacheSize=128m
# Set the extra sbt options
-Dsbt.log.format=true
-Dsbt.boot.directory=C:/sbt/.sbt/boot
-Dsbt.ivy.home=C:/sbt/.ivy2
-Dsbt.global.base=C:/sbt/.sbt
-Dsbt.repository.config=C:/sbt/conf/repository.properties
在 IntelliJ IDEA 中配置这些 -Dsbt 打头的参数行。
5. 在 IntelliJ IDEA 中创建 Play2 项目
推荐使用 LightBend Project Starter 来快速生成一个架构完备的 Play 框架。
注意,截止 2020 年 5 月份为止,该 seed 使用的默认 Scala 版本是 2.13.1,有很多依赖工具并没有适配 Scala 2.13 版本,因此需要自行在 build.sbt 中将版本降低到2.12.x版本。
6. 关于 Play2
6.1 映射请求的基本原理
浏览器内输入 url 请求某服务器上的 Play2 服务(默认是 9000 端口)。程序会去 routes 文件中根据 url 映射到 controllers 文件夹下的指定 Controller 的方法去执行。(注:遵守sbt构建项目时约定俗成的项目结构)
GET /doLogin controllers.LoginController.doLogin
示例:用户输入 localhost:9000/doLogin,该请求被转发到 controllers.LoginController.doLogin 去处理。根据业务逻辑的不同,可以返回一个简单内容,也可以返回一个 html 视图。并且这个行为被称之为 Action。
6.2 Play 基于 Action 处理请求
Controller返回的Action实际上是一个处理Http请求的函数。这个函数描述了一个抽象的请求接收并处理的过程:接收Request,处理请求,并返回Response。
Action{Request=>
//process this request.
Response
}
Response可以分为以下几种,注释后表示的是对应的HTTP请求状态码:
Ok(response) // 200
Redirect(url) // 302
BadRequest(response) // 400
Unauthorized(response) // 401
Forbidden(response) // 403
NotFound(response) // 404
InternalServerError(response) // 500
ServiceUnavailable(response) // 503
6.3 Twirl 模板被映射成模板函数
views文件夹下面放置着xxx.scala.html命名的文件,称之为模板文件,基于Play内置的Twirl引擎来进行渲染。它们被映射为了同名的views.html.xxx函数(Scala将函数视作是一等公民)。
Controller 在处理完业务之后将 login.scala.html 模板返回,则它会像调用函数一样,将一些变量 ( 如 title ) 拼装到模板 ( 如 login.scala.html ) 后,返回给浏览器展示:
def login() = Action {
//=>login.scala.html,并将"请登录"赋值给了title变量。
Ok(views.html.login(title = "请登录"))
}
而这个模板文件中存放着两类:等待动态插入的参数值(通过Controller在调用时传入参数),还有一个 html 文档。@ 后面对应着 views.html.login 的参数列表。不过这个函数的返回值比较特殊:它返回的是一个渲染 html 文件。可以像理解 Jsp 一样去理解它的工作流程。
@(title :String)
<!doctype html>
<html lang="en">
<head>...</head>
<body>...</body>
</html>
6.4 引用Play-Json包
根据官方文档的指示,引 play-json 包:📦Github Link
在 built.sbt 文件中添加依赖,并刷新 Refresh project :
libraryDependencies += "com.typesafe.play" %% "play-json" % "2.8.1"
给出一个 Json 的简单实例:
//play-json创建一个Json对象。
val returnJson: JsObject = Json.obj(
"name" -> "Lijunhu",
"emails" -> "376781642@qq.com",
"address" -> Json.obj(
"province" -> "HeiLongJiang",
"city" -> "Herbin"
)
)
从这个变量中提取 emails 字段:
val emails :String = (returnJson \ "emails")
.getOrElse(Json.obj("ERR-EMAILS-VALUE"->"null")).toString()
6.5 尝试 case class 和 JsonObj 之间的相互转换
//命名空间 | namespace
import play.api.libs.json._
//创建一个case class实例。 | create an instance of User object.
val userObj = User("02","Kate","JavaScript")
//创建一个符合case class格式的Json。 | create an instance of Json object in User format.
val userJson = Json.obj("_id"->"03","userName"->"Tim","department"->"Java")
//将userJson 转化为 User实例。 | convert a User obj to User Json.
val objValue: JsResult[User] = Json.fromJson[User](userJson)
//将User 转化为符合类型的 Json. | convert a User Json to User object.
val jsValue: JsValue = Json.toJson[User](userObj)
7. 使用 MongoDB 作为数据库 ( Windows 平台 )
📦官方链接:MongoDB
下载.msi文件可以省去自行配置环境变量的过程。注意,如果本机已经装载了旧版本的 MongoDB 数据库,请额外留意环境变量。
本框架后面使用的 play-mongo 组件要求 MongoDB 的版本至少为3.6,在安装过程中还可以选择额外安装一个界面管理工具 MongoDBCompassCommunity。
7.1 两种方式运行MongoDB服务
7.1.2 命令行下直接启动
在启动服务时指定数据的存储路径。
C:\mongodb\bin\mongod --dbpath c:\data\db
7.1.3 作为服务启动(推荐)
a. 需要创建一个mongodb.cfg文件,配置日志和数据的存储路径。
systemLog:
destination: file
path: c:\data\log\mongod.log
storage:
dbPath: c:\data\db
- 配置文件中不要使用
Tab符号,使用空格。 :符号后面有空格。- systemLog一项需要配置文件,而非目录。
b. 随后以管理员身份运行cmd,并输入:
C:\mongodb\bin\mongod.exe --config C:\mongodb\mongod.cfg --install
- --config参数需要输入绝对路径,文件名以实际配置为准。
c. 以管理员身份执行以下命令(非管理员会出现拒绝访问:5的问题):
>net start MongoDB # 开启服务
>net stop MongoDB # 关闭服务
>mongod --remove # 移除此安装的服务。
7.2 检查 MongoDB 是否启动成功
在浏览器中输入:http://127.0.0.1:27017/。MongoDB 的默认端口为 27017。若服务已正常启动,则浏览器页面显示:
It looks like you are trying to access MongoDB over HTTP on the native driver port.
7.3 连接本机 MongoDB
简单运行以下命令:
mongo
如果连接成功,则命令行窗口显示:
MongoDB shell version: x.x.x connecting to: test
7.4 创建 MongoDB 用户
创建一个 root 级别用户 admin,和一个数据库用户 std,赋予对该数据库的任意读写 ( dbOwner ) 权限。
> db.createUser({user:"admin",pwd:"ljh20176714",roles:["root"]})
Successfully added user: { "user" : "admin", "roles" : [ "root" ] }
> db.createUser({user:"std",pwd:"ljh20176714",roles:[{role:"dbOwner",db:"studentDB"}]})
Successfully added user: {
"user" : "std",
"roles" : [
{
"role" : "dbOwner",
"db" : "studentDB"
}
]
}
>
8. 使用 play mongo 搭建 MongDB 驱动
📦Github官网:Github tutorial
在build.sbt当中添加以下依赖:
libraryDependencies += "cn.playscala" % "play-mongo_2.12" % "0.3.0"
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)
注:当 sbt 反复提示 not found 错误时,除了切换镜像以外,要考虑到是否插件版本和目前的 Scala 版本不匹配。为了检查这一点,可以通过鼠标点击控制台显示的 repo1 镜像仓库链接查看支持的版本,然后进行适当调整。
随后,在 conf/application.conf 文件中,添加 MongoDB 设置。
# 配置数据库连接 | config your own dataBase source here.
mongodb.uri = "mongodb://localhost:27017/studentDB"
8.1 使用注解注入模型
有关该部分可参照 play mongo 官方的详细教程。
在 app/ 目录下建立 Module.scala,配置所有模型所在的目录在 app/models 下。
import cn.playscala.mongo.Mongo
import com.google.inject.AbstractModule
class Module extends AbstractModule{
override def configure() :Unit= {
Mongo.setModelsPackage("models")
}
}
对每个模型进行注入。注解内的 common-person 对应 MongoDB 数据库中的集合 collection。Person 则是该集合下的每一个 Document。
-
使用
case将这个伴生类声明为模板类,由底层来提供apply,unapply等伴生方法。 -
注意,对每个 Model,推荐自行定义其
_id。这个字段用作 MongDB 的主键。如果不主动声明,则 MongoDB 会生成一个随机串。
package models
import cn.playscala.mongo.annotations.Entity
@Entity("common-person")
case class Person(_id :String,name:String,sno :String)
8.2 建立模型-Json的转换
在app/models下创建包对象,提供 Json -> 模板类的**内部 ( 仅在 models 包下生效 ) **隐式转换:
package object models {
implicit val personFormat = Json.format[Person]
}
9. Play 的并发编程
Play 2.x 框架的并发机制依赖于 Akka ( Spark 底层的通讯机制也是依赖 Akka 完成的 )。当你使用 mongo 驱动执行一条查询语句时,它返回的是一个Future[T]。它有点类似一个视图 view,只有线程真正的执行这条语句时,我们才能才得到内部的 T。
因此为了等待某个线程为我们返回结果,我们使用 Await.result(..) 来阻塞它,
val maxAwaitTime = Duration(10000, "millis")
def studentList = Action {
//注意!它返回的是一个Future[List]。
//详情请参考Scala异步编程相关内容:Akka,Duration,Future.Await。
val eventualObjects: Future[List[JsObject]] = mongo.collection[Student].find[JsObject]().list()
//阻塞等待,并将最终结果返回。
val studentList: List[JsObject] = Await.result(eventualObjects, maxAwaitTime)
//Ok(JsArray(studentList))
Ok(views.html.students(studentList))
}
10. 部署 Play2 框架
Play 框架可以通过插件打包成 war 包,然后部署在 Tomcat 服务器下。但是在这里我们练习如何用 sbt 工具进行部署。在此之前,我们首先需要先配置 play2 框架的 Secret 。如果没有配置此项,则 Play 2 框架在部署环境中启动会出错。
为什么要配置它?官网给出的解释是用于抵御 CSRF 攻击的 token,或者是用于加密工具中。有两种配置方式,第一种是直接在项目文件中的
conf/application.conf添加(但官方并不推荐这种方式),另一种是在启动时作为参数添加。详细请参阅本小节附上的链接。
在 application.conf中配置以下行,密码值自行设定。
play.http.secret.key = "Your own secret"
额外的,如果对于运行在 Windows 平台的用户,最好在 build.sbt 处为你的项目添加 LauncherJarPlugin 插件。否则 cmd 在执行时可能会因为 *.bat 的参数过长而导致服务启动失败。
lazy val root = (project in file(".")).enablePlugins(PlayScala,LauncherJarPlugin)
只需要在项目根目录处执行 sbt dist 命令,sbt 便会自动地将 Play2 项目打包成一个 *.zip 压缩包。( 笔者使用 IntelliJ IDEA 开发,它在下方提供了一个 terminal 终端窗口。sbt 会将压缩包放入到..\target\universal目录下)。
我们只需要将这个压缩包提取出来,然后进入到 /bin 目录下,可以直接找到两个启动脚本:对于 Windows 环境,我们只需要直接打开 .bat 批处理文件;对于Linux 环境,我们 cd 到 bin 目录下执行它即可(这两个脚本文件都与你的项目名保持一致):
$ sudo ./{scalaProject}
额外提醒一点:在 Linux 环境下解压此压缩包时如果遇到了not in gzip format问题,则解压时的参数不要带上-z。
$ sudo tar -xvf {yourZip} -C {path}
📦解决Linux上解压jdk报错gzip: stdin: not in gzip format
11. 最简化版本的Github 链接
笔者基于 Play Scala Seed 生成了一个最“便携”版本的 Play-MongoDB 项目,没有任何额外的模型层,仅有基于一个简单 MongoDB 配置的MongoDBController。
12. 参考文献
- OSCHINA:Play 2.0用户指南
- 博客园:Play2.6-Scala指南
- 码云:基于PlayFramework2.5.4的后端程序
- Playframework官方
- IntelliJ IDEA创建Play2项目的几种方式
- 国内的PlayScala社区
- Play For Scala 开发指南(2.6.x)
- 未来可能会出现的跨域问题
- 依赖注入
- eBooks:Play for Scala
- OnlineView:Play for Scala
- Windows环境下安装MongoDB服务
- 安装MongoDB时遇见的问题
- playFrameWork专栏
- MvnRepository
- Jnuit Class not found...
- CSRF防护
- Json与Txt相互转换