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