Play! Framework 系列(四):DI 模式比较
本文由 Shaw 發(fā)表在 ScalaCool 團隊博客。
在 Play! Framework 系列(三)中我們簡單介紹了一下 Play 框架自身支持的兩種依賴注入(運行時依賴注入、編譯時依賴注入)。相信大家對 Play! 的依賴注入應該有所了解了。本文將詳細地介紹一些在日常開發(fā)中所采用的依賴注入的方式,以供大家進行合理地選擇。
Guice 和 手動注入
在上一篇文章中我們所介紹的「運行時依賴注入」以及「編譯時依賴注入」就是用的 Guice 以及手動注入,在這里就不作詳細介紹了,大家可以去看看上篇文章以及相應的 Demo
接下來我們介紹比較常用的依賴注入模式。
cake pattern(蛋糕模式)
我們首先介紹一下 Scala 中比較經(jīng)典的一種依賴注入的模式—— cake pattern(也叫“蛋糕模式”),“蛋糕模式”也屬于「編譯時依賴注入」的一種,她不需要依賴 DI 框架。那 “蛋糕模式” 是如何實現(xiàn)的呢?我們知道,在 Scala 中,多個 trait(特質(zhì))能夠 “混入” 到 class 中,這樣在某個 class 中我們就能夠得到所有 trait 中定義的東西了。“蛋糕模式”就是基于此種特性而實現(xiàn)的。
接下來我們就通過一個例子來了解一下“蛋糕模式”:
我們需要在頁面上顯示一個包含所有會員信息的會員列表,需要顯示的內(nèi)容有:
需求很簡單,接下來我們用代碼組織一下業(yè)務:
我們需要從數(shù)據(jù)庫中查詢「會員卡」以及「會員」的信息,所以這里我們首先定義一個數(shù)據(jù)庫連接的類:DatabaseAccessService 來對相應的數(shù)據(jù)庫進行操作:
trait DatabaseAccessServiceComp {val databaseAccessService = new DatabaseAccessService() }class DatabaseAccessService{... } 復制代碼大家可能會發(fā)現(xiàn),在我們之前文章中的 service 中并沒有定義 trait,而這里卻定義了,并且在 trait 中,我們實例化了 DatabaseAccessService, 這就是“蛋糕模式”中所需要的,現(xiàn)在看好像并沒有什么卵用,別急,等我們將所有的 service 都定義好了,她就有用了。
接下來我們定義 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 沒有什么區(qū)別,因為上面兩個 service 都需要依賴 DatabaseAccessService,所以在特質(zhì)中用「自身類型」來將其混入,如果需要多個依賴,可以這樣寫:
this DatabaseAccessServiceComp with BarComp with FooComp => 復制代碼最后我們需要定義一個 WxcardController,來將數(shù)據(jù)傳遞到相應的頁面上去:
class WxcardController (cc: ControllerComponents,wxcardService: WxcardService,wxcardMemberService: WxcardMemberService ) extends AbstractController(cc) {...} 復制代碼可以看到 WxcardController 需要依賴我們上面定義的一些 service,那么在蛋糕模式下,我們怎樣才能將這些依賴注入到 WxcardController 中呢,由于“蛋糕模式”也是「編譯時依賴注入」的一種,那么我們可以參考上一篇文章中所采用的方式:
同樣,我們需要實現(xiàn)自己的 ApplicationLoader:
//定義 load 那部分代碼省略了,大家可以去看 Demo ...class MyComponents(context: ApplicationLoader.Context)extends BuiltInComponentsFromContext(context)with play.filters.HttpFiltersComponentswith DatabaseAccessServiceCompwith WxcardServiceCompwith WxcardMemberServiceComp {lazy val wxcardController = new WxcardController(controllerComponents, wxcardService, wxcardMemberService)lazy val router: Router = new Routes(httpErrorHandler, wxcardController) } 復制代碼通過上面的代碼,就完成了注入,可以看到我們定義的所有 xxxServiceComp 特質(zhì)都被混入到了 MyComponents 中,這樣,當 Play加載時,我們所定義的 service 就都在這里被實例化了,為什么呢?因為我們在定義 xxxServiceComp 時,都會有這么一行代碼:
val xxxService = new XxxService() 復制代碼這就是為什么我們之前要在每個 service 中都定義一個 trait,因為 Scala 中的 class 可以混入多個 trait,在這里,我們可以將所有需要的依賴都混入到 MyComponents 中,然后實現(xiàn)注入。
至于為什么要叫“蛋糕模式”,我個人是這么理解的: 我們定義的 xxxServiceComp 比如 WxcardServiceComp 相當于蛋糕中的某一層,而那些需要被多次依賴的 xxxServiceComp,比如上面定義的 DatabaseAccessServiceComp 可以看作是蛋糕中的調(diào)味料(比如水果,巧克力啥的),將這些蛋糕一層一層地放在一起,然后再混入一些調(diào)味料,就組成了一個大的蛋糕—— MyComponents。
可以看到“蛋糕模式”中,我們需要寫非常多的樣板代碼,要為每個 service 都定義一個 trait,感覺心很累,那么接下來我們就介紹一種比較輕巧而又簡潔的的方式。
macwire
macwire 是基于 「Scala 宏」來實現(xiàn)的,我們使用她可以讓依賴注入變得非常簡單,并且使我們的代碼量減少許多。接下來,我們就通過 macwire 來實現(xiàn)一下上面的例子。
首先在項目中引入 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:
//定義 DatabaseAccessServiceclass DatabaseAccessService{... }//定義 WxcardServiceclass WxcardService(databaseAccessService: DatabaseAccessService) {... }//定義 WxcardMembrServiceclass WxcardMemberService(databaseAccessService: DatabaseAccessService) {... } 復制代碼可以看到,我們現(xiàn)在就不需要定義 trait 了,接下來,定義 WxcardController:
class WxcardController (cc: ControllerComponents,wxcardService: WxcardService,wxcardMemberService: WxcardMemberService ) extends AbstractController(cc) {...} 復制代碼controller 的定義和上面的一樣,接下來,我們就使用 macwire 來實現(xiàn)依賴注入,macwire 也是「編譯時依賴注入」的一種,所以我們同樣需要實現(xiàn) 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,這樣就完成了實例化,并且也不需要指定依賴的參數(shù),macwire 會自動幫我們完成實例化和注入:
比如上面的
lazy val databaseAccessService = wire[DatabaseAccessService] lazy val wxcardService = wire[WxcardService] lazy val wxcardMemberService = wire[WxcardMemberService] lazy val wxcardController = wire[WxcardController] 復制代碼macwire 就幫我們轉(zhuǎn)化成了:
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 都會幫我們?nèi)ネ瓿?#xff0c;macwire 在實例化某個類的時候,會去當前文件或者與當前文件有關(guān)的代碼中查找相關(guān)的依賴,找到了就完成注入,若沒有找到說明該依賴沒有被定義過,或者沒有正確引入。
在日常開發(fā)中,我們會創(chuàng)建很多個 service,將所有的 service 放在 MyComponents 中實例化會使得代碼顯得很臃腫,而且也不便于維護。通常我們會專門定義一個 Module 來組織這些 service:
package configimport 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.HttpFiltersComponentswith config.ServicesModule {lazy val wxcardController = wire[WxcardController]lazy val router: Router = {val prefix = "/"wire[Routes]} } 復制代碼可以看到 macwire 使用起來非常簡單,并且能夠簡化我們的依賴注入。在我們的項目中所采用的是 macwire,所以推薦大家使用 macwire。
結(jié)語
關(guān)于 Play 中的「依賴注入」到這里就結(jié)束了,希望能夠給大家一些幫助,另外 Play 系列的文章從上一篇到現(xiàn)在拖了太久了,非常抱歉,感謝一直以來的關(guān)注,后面我會加快寫作節(jié)奏的,本文的例子請戳源碼鏈接。
總結(jié)
以上是生活随笔為你收集整理的Play! Framework 系列(四):DI 模式比较的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RabbitMQ封装实战
- 下一篇: SpringData ES中字段名和索引