本文由 Shaw 发表在 ScalaCool 团队博客。git
在 Play! Framework 系列(三)中咱们简单介绍了一下 Play 框架自身支持的两种依赖注入(运行时依赖注入、编译时依赖注入)。相信你们对 Play! 的依赖注入应该有所了解了。本文将详细地介绍一些在平常开发中所采用的依赖注入的方式,以供你们进行合理地选择。github
在上一篇文章中咱们所介绍的「运行时依赖注入」以及「编译时依赖注入」就是用的 Guice 以及手动注入,在这里就不做详细介绍了,你们能够去看看上篇文章以及相应的 Demo数据库
接下来咱们介绍比较经常使用的依赖注入模式。框架
咱们首先介绍一下 Scala 中比较经典的一种依赖注入的模式—— cake pattern(也叫“蛋糕模式”),“蛋糕模式”也属于「编译时依赖注入」的一种,她不须要依赖 DI 框架。那 “蛋糕模式” 是如何实现的呢?咱们知道,在 Scala 中,多个 trait(特质)可以 “混入” 到 class 中,这样在某个 class 中咱们就可以获得全部 trait 中定义的东西了。“蛋糕模式”就是基于此种特性而实现的。ide
接下来咱们就经过一个例子来了解一下“蛋糕模式”:ui
咱们须要在页面上显示一个包含全部会员信息的会员列表,须要显示的内容有:this
需求很简单,接下来咱们用代码组织一下业务:spa
咱们须要从数据库中查询「会员卡」以及「会员」的信息,因此这里咱们首先定义一个数据库链接的类:DatabaseAccessService 来对相应的数据库进行操做:scala
trait DatabaseAccessServiceComp {
val databaseAccessService = new DatabaseAccessService()
}
class DatabaseAccessService{
...
}
复制代码
你们可能会发现,在咱们以前文章中的 service 中并无定义 trait,而这里却定义了,而且在 trait 中,咱们实例化了 DatabaseAccessService, 这就是“蛋糕模式”中所须要的,如今看好像并无什么卵用,别急,等咱们将全部的 service 都定义好了,她就有用了。code
接下来咱们定义 WxcardService 以及 WxcardMemberService:
//定义 WxcardService
trait WxcardServiceComp {
this: DatabaseAccessServiceComp =>
val wxcardService = new WxcardService(databaseAccessService)
}
class WxcardService(databaseAccessService: DatabaseAccessService) {
...
}
//定义 WxcardMembrService
trait WxcardMemberServiceComp {
this: DatabaseAccessServiceComp =>
val wxcardMemberService = new WxcardMemberService(databaseAccessService)
}
class WxcardMemberService(databaseAccessService: DatabaseAccessService) {
...
}
复制代码
写法与上面定义的 DatabaseAccessService 没有什么区别,由于上面两个 service 都须要依赖 DatabaseAccessService,因此在特质中用「自身类型」来将其混入,若是须要多个依赖,能够这样写:
this DatabaseAccessServiceComp with BarComp with FooComp =>
复制代码
最后咱们须要定义一个 WxcardController,来将数据传递到相应的页面上去:
class WxcardController ( cc: ControllerComponents, wxcardService: WxcardService, wxcardMemberService: WxcardMemberService ) extends AbstractController(cc) {...}
复制代码
能够看到 WxcardController 须要依赖咱们上面定义的一些 service,那么在蛋糕模式下,咱们怎样才能将这些依赖注入到 WxcardController 中呢,因为“蛋糕模式”也是「编译时依赖注入」的一种,那么咱们能够参考上一篇文章中所采用的方式:
一样,咱们须要实现本身的 ApplicationLoader:
//定义 load 那部分代码省略了,你们能够去看 Demo
...
class MyComponents(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context)
with play.filters.HttpFiltersComponents
with DatabaseAccessServiceComp
with WxcardServiceComp
with WxcardMemberServiceComp {
lazy val wxcardController = new WxcardController(controllerComponents, wxcardService, wxcardMemberService)
lazy val router: Router = new Routes(httpErrorHandler, wxcardController)
}
复制代码
经过上面的代码,就完成了注入,能够看到咱们定义的全部 xxxServiceComp 特质都被混入到了 MyComponents 中,这样,当 Play加载时,咱们所定义的 service 就都在这里被实例化了,为何呢?由于咱们在定义 xxxServiceComp 时,都会有这么一行代码:
val xxxService = new XxxService()
复制代码
这就是为何咱们以前要在每一个 service 中都定义一个 trait,由于 Scala 中的 class 能够混入多个 trait,在这里,咱们能够将全部须要的依赖都混入到 MyComponents 中,而后实现注入。
至于为何要叫“蛋糕模式”,我我的是这么理解的: 咱们定义的 xxxServiceComp 好比 WxcardServiceComp 至关于蛋糕中的某一层,而那些须要被屡次依赖的 xxxServiceComp,好比上面定义的 DatabaseAccessServiceComp 能够看做是蛋糕中的调味料(好比水果,巧克力啥的),将这些蛋糕一层一层地放在一块儿,而后再混入一些调味料,就组成了一个大的蛋糕—— MyComponents。
能够看到“蛋糕模式”中,咱们须要写很是多的样板代码,要为每一个 service 都定义一个 trait,感受心很累,那么接下来咱们就介绍一种比较轻巧而又简洁的的方式。
macwire 是基于 「Scala 宏」来实现的,咱们使用她可让依赖注入变得很是简单,而且使咱们的代码量减小许多。接下来,咱们就经过 macwire 来实现一下上面的例子。
首先在项目中引入 macwire,在 build.sbt 文件中增长一行依赖:
libraryDependencies ++= Seq(
"org.scalatestplus.play" %% "scalatestplus-play" % "3.0.0-M3" % Test,
//在这里添加 macwire 的依赖
"com.softwaremill.macwire" %% "macros" % "2.3.0" % Provided,
)
复制代码
而后定义 service:
//定义 DatabaseAccessService
class DatabaseAccessService{
...
}
//定义 WxcardService
class WxcardService(databaseAccessService: DatabaseAccessService) {
...
}
//定义 WxcardMembrService
class WxcardMemberService(databaseAccessService: DatabaseAccessService) {
...
}
复制代码
能够看到,咱们如今就不须要定义 trait 了,接下来,定义 WxcardController:
class WxcardController ( cc: ControllerComponents, wxcardService: WxcardService, wxcardMemberService: WxcardMemberService ) extends AbstractController(cc) {...}
复制代码
controller 的定义和上面的同样,接下来,咱们就使用 macwire 来实现依赖注入,macwire 也是「编译时依赖注入」的一种,因此咱们一样须要实现 ApplicationLoader:
import com.softwaremill.macwire._
...
class MyComponents(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context)
with play.filters.HttpFiltersComponents {
lazy val databaseAccessService = wire[DatabaseAccessService]
lazy val wxcardService = wire[WxcardService]
lazy val wxcardMemberService = wire[WxcardMemberService]
lazy val wxcardController = wire[WxcardController]
lazy val router: Router = {
val prefix = "/"
wire[Routes]
}
}
复制代码
在上面的代码中,咱们只须要将相应的依赖经过下面的方式实例化就能够了:
lazy val wxcardService = wire[WxcardService]
复制代码
就是在类型外面添加了一个 wire
,这样就完成了实例化,而且也不须要指定依赖的参数,macwire 会自动帮咱们完成实例化和注入:
好比上面的
lazy val databaseAccessService = wire[DatabaseAccessService]
lazy val wxcardService = wire[WxcardService]
lazy val wxcardMemberService = wire[WxcardMemberService]
lazy val wxcardController = wire[WxcardController]
复制代码
macwire 就帮咱们转化成了:
lazy val databaseAccessService = new DatabaseAccessService()
lazy val wxcardService = new WxcardService(databaseAccessService)
lazy val wxcardMemberService = new WxcardMemberService(databaseAccessService)
lazy val wxcardController = new WxcardController(controllerComponents, wxcardService, wxcardMemberService)
复制代码
咱们只须要在定义某个类的时候声明咱们须要哪些依赖,实例化和注入 macwire 都会帮咱们去完成,macwire 在实例化某个类的时候,会去当前文件或者与当前文件有关的代码中查找相关的依赖,找到了就完成注入,若没有找到说明该依赖没有被定义过,或者没有正确引入。
在平常开发中,咱们会建立不少个 service,将全部的 service 放在 MyComponents 中实例化会使得代码显得很臃肿,并且也不便于维护。一般咱们会专门定义一个 Module 来组织这些 service:
package config
import com.softwaremill.macwire._
import services._
trait ServicesModule {
lazy val databaseAccessService = wire[DatabaseAccessService]
lazy val wxcardService = wire[WxcardService]
lazy val wxcardMemberService = wire[WxcardMemberService]
}
复制代码
这里咱们新建了一个 ServiceModule.scala 文件来将组织这些 service。
那么上面的 ApplicationLoader 文件就能够这样去写:
import com.softwaremill.macwire._
...
class MyComponents(context: ApplicationLoader.Context)
extends BuiltInComponentsFromContext(context)
with play.filters.HttpFiltersComponents
with config.ServicesModule {
lazy val wxcardController = wire[WxcardController]
lazy val router: Router = {
val prefix = "/"
wire[Routes]
}
}
复制代码
能够看到 macwire 使用起来很是简单,而且可以简化咱们的依赖注入。在咱们的项目中所采用的是 macwire,因此推荐你们使用 macwire。
关于 Play 中的「依赖注入」到这里就结束了,但愿可以给你们一些帮助,另外 Play 系列的文章从上一篇到如今拖了过久了,很是抱歉,感谢一直以来的关注,后面我会加快写做节奏的,本文的例子请戳源码连接。