Play! Framework 系列(三):依賴注入

本文由 Shaw 發表在 ScalaCool 團隊博客。html

Play! Framework 系列(二)中咱們介紹了 Play 的項目結構。在平常處理業務邏輯的時候,咱們都會用到依賴注入,本文將介紹一下 Play! 中的依賴注入以及如何合理地去使用她。java

爲何要使用「依賴注入」

在許多 Java 框架中,「依賴注入」早已不是一個陌生的技術,Play 框架從 2.4 開始推薦使用 Guice 來做爲依賴注入。git

採用依賴注入最大的好處就是爲了「解耦」,舉個栗子:github

上一篇文章的例子中,咱們實現了一個 EmployeeService 用來對公司的員工進行操做:數據庫

package services

import models._

class EmployeeSerivce{
  ...
}
複製代碼

在以前的實現中,咱們沒有加入數據庫的操做,那麼如今咱們想要引入一個數據庫鏈接的類:DatabaseAccessService 來對數據庫進行鏈接以便咱們對相應的數據庫表進行操做,則:api

新建一個數據庫鏈接操做的 Service:mvc

package services

class DatabaseAccessService{}
複製代碼

EmployeeSerivce 須要依賴 DatabaseAccessService:app

package services

import models._

class EmployeeSerivce(db: DBService){
  ...
}
複製代碼

好了,如今咱們須要在 EmployeeController 中使用 EmployeeSerivce,若是不採用依賴注入,則:框架

class EmployeeController @Inject() ( cc: ControllerComponents ) extends AbstractController(cc) {
  val db = new DatabaseAccessService()
  val employeeSerivce = new EmployeeSerivce(db)

  def employeeList = Action { implicit request: Request[AnyContent] =>
    val employees = employeeSerivce.getEmployees()
    Ok(views.html.employeeList(employees))
  }
}
複製代碼

能夠看到,爲了實例化 EmployeeSerivce,DatabaseAccessService 也須要實例化,若是隨着需求的增長,EmployeeSerivce 所須要依賴的東西增長,那麼咱們每次實例化 EmployeeSerivce 的時候都須要將她的依賴也實例化一遍,並且她的依賴也有可能會依賴其餘東西,這樣就使得咱們的代碼變得很是冗餘,也極難維護。ui

爲了解決這一問題,咱們引入了依賴注入,Play支持兩種方式的依賴注入,分別是:「運行時依賴注入」以及「編譯時依賴注入」,接下來咱們就經過這兩種依賴注入來解決咱們上面提出的問題。

運行時依賴注入(runtime dependency)

Play 的運行時依賴注入默認採用 Guice,關於 Guice,咱們後面的文章當中會介紹,這裏只須要知道她。爲了支持 Guice 以及其餘的運行時依賴注入框架,Play 提供了大量的內置組件。詳見 play.api.inject

那麼在 Play 中咱們將如何使用這種依賴注入呢?回到咱們文章剛開始講的那個栗子中,如今咱們經過依賴注入的方式來從新組織咱們的代碼:

首先 EmployeeSerivce 須要依賴 DatabaseAccessService,這裏其實就存在一個「依賴注入」,那咱們這樣去實現:

package services

import models._
import javax.inject._

class EmployeeSerivce @Inject() (db: DBService){
  ...
}
複製代碼

在上面的代碼中,咱們引入了 import javax.inject._,而且能夠看到多了一個 @Inject() 註解,咱們實現運行時依賴注入就採用該註解。

那麼在 EmployeeController 中,咱們的代碼就變成了:

class EmployeeController @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 在運行時就會將咱們須要的依賴注入到相應的組件中去。

tip:@Inject 必須放在類名的後面,構造參數的前面。

「運行時依賴注入」,顧名思義就是在程序運行的時候進行依賴注入,可是她不能在編譯時進行校驗,爲了能讓程序在編譯時就能實現對依賴注入的校驗, Play支持了「編譯時依賴注入」。

編譯時依賴注入(compile time dependency injection)

爲了實現編譯時依賴注入,咱們須要知道 Play 提供的一個特質:ApplicationLoader,該特質中的 load 方法將會在程序啓動的時候加載咱們的應用程序,在這個過程當中,Play 框架自己以及咱們本身的程序代碼所依賴的東西都會被實例化。

默認狀況下,Play 提供了一個 Guice 模塊,該模塊下的 GuiceApplicationBuilder 會根據 Play 框架給定的 context 去將該程序所依賴的全部組件聯繫在一塊兒。

若是咱們要自定義 ApplicationLoader,咱們也須要一個像 GuiceApplicationBuilder 的東西,好在 Play 提供了這麼一個東西,那就是:BuiltInComponentsFromContext,咱們能夠經過繼承這個類來實現咱們本身的 ApplicationLoader。

接下來咱們經過相應的代碼來做進一步的解釋:

import controllers._
import play.api._
import play.api.routing.Router
import services._
import router.Routes


//自定義 ApplicationLoader
class MyApplicationLoader extends ApplicationLoader {
  def load(context: Context): Application = {
    new MyComponents(context).application
  }
}

class MyComponents(context: Context)
    extends BuiltInComponentsFromContext(context)
    with play.filters.HttpFiltersComponents {

  lazy val databaseAccessService = new DatabaseAccessService

  lazy val employeeSerivce = new EmployeeSerivce(databaseAccessService)

  lazy val employeeController = new EmployeeController(employeeSerivce, controllerComponents)

  lazy val router: Router = new Routes(httpErrorHandler, employeeController)
}
複製代碼

咱們經過繼承 BuiltInComponentsFromContext 使得程序可以根據 Play 所提供的 context 來加載 Play 框架自己所須要的一些組件。

那麼回到咱們的「編譯時的依賴注入」中來,能夠看到在 class MyComponents 中,咱們將全部的 service 都實例化了,而且將這些實例注入到相應的依賴她們的模塊中:

//將兩個 service 實例化
lazy val databaseAccessService = new DatabaseAccessService

//EmployeeSerivce 依賴 DatabaseAccessService,將實例 databaseAccessService 注入其中
lazy val employeeSerivce = new EmployeeSerivce(databaseAccessService)

//將 employeeSerivce 注入到 employeeController 中
lazy val employeeController = new EmployeeController(employeeSerivce, controllerComponents)
複製代碼

使用 BuiltInComponentsFromContext 時,咱們須要本身實現一下 router:

lazy val router: Router = new Routes(httpErrorHandler, employeeController)
複製代碼

tip:須要注意的是,若是咱們實現了本身的 ApplicationLoader,咱們須要在 application.conf 文件中聲明一下:

play.application.loader = MyApplicationLoader
複製代碼

經過自定義 ApplicationLoader 咱們就實現了編譯時期的依賴注入,那麼 EmployeeSerivce 就變成了:

package services

import models._

class EmployeeSerivce (db: DBService){
  ...
}
複製代碼

能夠看到, 這裏就省去了 @Inject() 註解。

一樣的,對於 EmployeeController:

package controllers

import play.api._
import play.api.mvc._
import models._
import services._

// 沒有了 @Inject() 註解
class EmployeeController ( employeeSerivce: EmployeeSerivce, cc: ControllerComponents ) extends AbstractController(cc) {
  ...
}
複製代碼

經過使用編譯時期的依賴注入,咱們只須要在將全部的依賴實例化一次就夠了,而且使用這種方式,咱們可以在編譯時期就能發現程序的一些異常。一樣的,使用該方法也會有一些問題,就是咱們須要寫許多樣板代碼。另外本文的編譯時期的依賴注入徹底是本身手動注入的,看上去也比較繁瑣,不是那麼直觀,若是要使用更優雅的方式,咱們可使用 macwire,這個咱們在後面的文章中會詳細講解。

結語

本文簡單介紹了一下 Play 支持的兩種依賴注入的模式,文中提到的一些第三方依賴注入的框架咱們會在後面的文章中詳細介紹。本文的例子請戳源碼連接

相關文章
相關標籤/搜索