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 系列的文章從上一篇到如今拖了過久了,很是抱歉,感謝一直以來的關注,後面我會加快寫做節奏的,本文的例子請戳源碼連接

相關文章
相關標籤/搜索