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