Play! Framework 系列(三):依赖注入
在Play! Framework 系列(二)中我們介紹了 Play 的項目結(jié)構(gòu)。在日常處理業(yè)務(wù)邏輯的時候,我們都會用到依賴注入,本文將介紹一下 Play! 中的依賴注入以及如何合理地去使用她。
為什么要使用「依賴注入」
在許多 Java 框架中,「依賴注入」早已不是一個陌生的技術(shù),Play 框架從 2.4 開始推薦使用?Guice?來作為依賴注入。
采用依賴注入最大的好處就是為了「解耦」,舉個栗子:
在上一篇文章的例子中,我們實現(xiàn)了一個 EmployeeService 用來對公司的員工進(jìn)行操作:
1 2 3 4 5 6 7 | package services import models._ classEmployeeSerivce{ ... } |
在之前的實現(xiàn)中,我們沒有加入數(shù)據(jù)庫的操作,那么現(xiàn)在我們想要引入一個數(shù)據(jù)庫連接的類:DatabaseAccessService 來對數(shù)據(jù)庫進(jìn)行連接以便我們對相應(yīng)的數(shù)據(jù)庫表進(jìn)行操作,則:
新建一個數(shù)據(jù)庫連接操作的 Service:
1 2 3 | package services classDatabaseAccessService{} |
EmployeeSerivce 需要依賴 DatabaseAccessService:
1 2 3 4 5 6 7 | package services import models._ classEmployeeSerivce(db: DBService){ ... } |
好了,現(xiàn)在我們需要在 EmployeeController 中使用 EmployeeSerivce,如果不采用依賴注入,則:
1 2 3 4 5 6 7 8 9 10 11 | classEmployeeController@Inject() ( cc: ControllerComponents ) extends AbstractController(cc) { val db = newDatabaseAccessService() val employeeSerivce = newEmployeeSerivce(db) def employeeList = Action { implicit request: Request[AnyContent] => val employees = employeeSerivce.getEmployees() Ok(views.html.employeeList(employees)) } } |
可以看到,為了實例化 EmployeeSerivce,DatabaseAccessService 也需要實例化,如果隨著需求的增加,EmployeeSerivce 所需要依賴的東西增加,那么我們每次實例化 EmployeeSerivce 的時候都需要將她的依賴也實例化一遍,而且她的依賴也有可能會依賴其他東西,這樣就使得我們的代碼變得非常冗余,也極難維護(hù)。
為了解決這一問題,我們引入了依賴注入,Play支持兩種方式的依賴注入,分別是:「運行時依賴注入」以及「編譯時依賴注入」,接下來我們就通過這兩種依賴注入來解決我們上面提出的問題。
運行時依賴注入(runtime dependency)
Play 的運行時依賴注入默認(rèn)采用?Guice,關(guān)于 Guice,我們后面的文章當(dāng)中會介紹,這里只需要知道她。為了支持 Guice 以及其他的運行時依賴注入框架,Play 提供了大量的內(nèi)置組件。詳見?play.api.inject。
那么在 Play 中我們將如何使用這種依賴注入呢?回到我們文章剛開始講的那個栗子中,現(xiàn)在我們通過依賴注入的方式來重新組織我們的代碼:
首先 EmployeeSerivce 需要依賴 DatabaseAccessService,這里其實就存在一個「依賴注入」,那我們這樣去實現(xiàn):
1 2 3 4 5 6 7 8 | package services import models._ import javax.inject._ classEmployeeSerivce@Inject() (db: DBService){ ... } |
在上面的代碼中,我們引入了?import javax.inject._,并且可以看到多了一個?@Inject()?注解,我們實現(xiàn)運行時依賴注入就采用該注解。
那么在 EmployeeController 中,我們的代碼就變成了:
1 2 3 4 5 6 7 8 9 | classEmployeeController@Inject() ( employeeSerivce: EmployeeSerivce, cc: ControllerComponents ) extends AbstractController(cc) { def employeeList = Action { implicit request: Request[AnyContent] => val employees = employeeSerivce.getEmployees() Ok(views.html.employeeList(employees)) } } |
可以看到我們不需要再去寫那么多的實例了,我們只要在需要某種依賴的地方聲明一下我們需要什么樣的依賴, Play 在運行時就會將我們需要的依賴注入到相應(yīng)的組件中去。
tip:@Inject?必須放在類名的后面,構(gòu)造參數(shù)的前面。
「運行時依賴注入」,顧名思義就是在程序運行的時候進(jìn)行依賴注入,但是她不能在編譯時進(jìn)行校驗,為了能讓程序在編譯時就能實現(xiàn)對依賴注入的校驗, Play支持了「編譯時依賴注入」。
編譯時依賴注入(compile time dependency injection)
為了實現(xiàn)編譯時依賴注入,我們需要知道 Play 提供的一個特質(zhì):ApplicationLoader,該特質(zhì)中的 load 方法將會在程序啟動的時候加載我們的應(yīng)用程序,在這個過程中,Play 框架本身以及我們自己的程序代碼所依賴的東西都會被實例化。
默認(rèn)情況下,Play 提供了一個 Guice 模塊,該模塊下的 GuiceApplicationBuilder 會根據(jù) Play 框架給定的 context 去將該程序所依賴的所有組件聯(lián)系在一起。
如果我們要自定義 ApplicationLoader,我們也需要一個像 GuiceApplicationBuilder 的東西,好在 Play 提供了這么一個東西,那就是:BuiltInComponentsFromContext,我們可以通過繼承這個類來實現(xiàn)我們自己的 ApplicationLoader。
接下來我們通過相應(yīng)的代碼來作進(jìn)一步的解釋:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import controllers._ import play.api._ import play.api.routing.Router import services._ import router.Routes //自定義 ApplicationLoader classMyApplicationLoaderextendsApplicationLoader { def load(context: Context): Application = { newMyComponents(context).application } } classMyComponents(context: Context) extendsBuiltInComponentsFromContext(context) with play.filters.HttpFiltersComponents { lazyval databaseAccessService = newDatabaseAccessService lazyval employeeSerivce = newEmployeeSerivce(databaseAccessService) lazyval employeeController = newEmployeeController(employeeSerivce, controllerComponents) lazyval router: Router = newRoutes(httpErrorHandler, employeeController) } |
我們通過繼承 BuiltInComponentsFromContext 使得程序能夠根據(jù) Play 所提供的 context 來加載 Play 框架本身所需要的一些組件。
那么回到我們的「編譯時的依賴注入」中來,可以看到在 class MyComponents 中,我們將所有的 service 都實例化了,并且將這些實例注入到相應(yīng)的依賴她們的模塊中:
1 2 3 4 5 6 7 8 | //將兩個 service 實例化 lazyval databaseAccessService = newDatabaseAccessService //EmployeeSerivce 依賴 DatabaseAccessService,將實例 databaseAccessService 注入其中 lazyval employeeSerivce = newEmployeeSerivce(databaseAccessService) //將 employeeSerivce 注入到 employeeController 中 lazyval employeeController = newEmployeeController(employeeSerivce, controllerComponents) |
使用 BuiltInComponentsFromContext 時,我們需要自己實現(xiàn)一下 router:
1 | lazyval router: Router = newRoutes(httpErrorHandler, employeeController) |
tip:需要注意的是,如果我們實現(xiàn)了自己的 ApplicationLoader,我們需要在?application.conf?文件中聲明一下:
1 | play.application.loader = MyApplicationLoader |
通過自定義 ApplicationLoader 我們就實現(xiàn)了編譯時期的依賴注入,那么 EmployeeSerivce 就變成了:
1 2 3 4 5 6 7 | package services import models._ classEmployeeSerivce (db: DBService){ ... } |
可以看到,這里就省去了?@Inject()?注解。
同樣的,對于 EmployeeController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package controllers import play.api._ import play.api.mvc._ import models._ import services._ // 沒有了 @Inject() 注解 classEmployeeController ( employeeSerivce: EmployeeSerivce, cc: ControllerComponents ) extends AbstractController(cc) { ... } |
通過使用編譯時期的依賴注入,我們只需要在將所有的依賴實例化一次就夠了,并且使用這種方式,我們能夠在編譯時期就能發(fā)現(xiàn)程序的一些異常。同樣的,使用該方法也會有一些問題,就是我們需要寫許多樣板代碼。另外本文的編譯時期的依賴注入完全是自己手動注入的,看上去也比較繁瑣,不是那么直觀,如果要使用更優(yōu)雅的方式,我們可以使用?macwire,這個我們在后面的文章中會詳細(xì)講解。
結(jié)語
本文簡單介紹了一下 Play 支持的兩種依賴注入的模式,文中提到的一些第三方依賴注入的框架我們會在后面的文章中詳細(xì)介紹。本文的例子請戳源碼鏈接
本文轉(zhuǎn)載自:https://scala.cool/2017/11/play-3/
總結(jié)
以上是生活随笔為你收集整理的Play! Framework 系列(三):依赖注入的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Play! Framework 系列(二
- 下一篇: 从 Java 到 Scala(一):面向