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相互转换