Play! Framework 系列(四):DI 模式比较

本文由 Shaw 发表在 ScalaCool 团队博客。git

Play! Framework 系列(三)中咱们简单介绍了一下 Play 框架自身支持的两种依赖注入(运行时依赖注入、编译时依赖注入)。相信你们对 Play! 的依赖注入应该有所了解了。本文将详细地介绍一些在平常开发中所采用的依赖注入的方式,以供你们进行合理地选择。github

Guice 和 手动注入

上一篇文章中咱们所介绍的「运行时依赖注入」以及「编译时依赖注入」就是用的 Guice 以及手动注入,在这里就不做详细介绍了,你们能够去看看上篇文章以及相应的 Demo数据库

接下来咱们介绍比较经常使用的依赖注入模式。框架

cake pattern(蛋糕模式)

咱们首先介绍一下 Scala 中比较经典的一种依赖注入的模式—— cake pattern(也叫“蛋糕模式”),“蛋糕模式”也属于「编译时依赖注入」的一种,她不须要依赖 DI 框架。那 “蛋糕模式” 是如何实现的呢?咱们知道,在 Scala 中,多个 trait(特质)可以 “混入” 到 class 中,这样在某个 class 中咱们就可以获得全部 trait 中定义的东西了。“蛋糕模式”就是基于此种特性而实现的。ide

接下来咱们就经过一个例子来了解一下“蛋糕模式”:ui

咱们须要在页面上显示一个包含全部会员信息的会员列表,须要显示的内容有:this

  1. 会员信息
  2. 会员卡的信息

需求很简单,接下来咱们用代码组织一下业务: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

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 系列的文章从上一篇到如今拖了过久了,很是抱歉,感谢一直以来的关注,后面我会加快写做节奏的,本文的例子请戳源码连接

相关文章
相关标签/搜索