文件

§編譯時間依賴注入

Play 提供了一種執行時間依賴注入機制,也就是在執行時間才連接依賴項的依賴注入。這種方法有優缺點,主要優點是將樣板程式碼減到最少,主要缺點是應用程式的建構並未在編譯時間驗證。

在 Scala 開發中很受歡迎的另一種方法是使用編譯時期依賴性注入。最簡單來說,編譯時期 DI 可以透過手動建構和連接依賴性來達成。還有其他更進階的技術和工具,例如基於巨集的自動連接工具、暗示自動連接技術和各種形式的 cake 模式。所有這些都可以輕鬆地在建構函式和手動連接之上實作,因此 Play 對編譯時期依賴性注入的支援是透過提供公開建構函式和工廠方法作為 API 來提供的。

除了提供公開建構函式和工廠方法之外,Play 所有開箱即用的模組都提供一些特徵,用於實作 cake 模式的輕量級形式,以提供便利性。這些建構在公開建構函式之上,而且完全是選用的。在某些應用程式中,它們可能不適合使用,但在許多應用程式中,它們會是很便利的機制,用於連接 Play 提供的元件。這些特徵遵循命名慣例,即特徵名稱以 Components 結尾,例如,基於 HikariCP 的 DB API 預設實作提供一個稱為 HikariCPComponents 的特徵。

如果您不熟悉編譯時期 DI 或一般的 DI,建議閱讀 Adam Warski 的 Scala DI 指南,其中討論了編譯時期 DI 的一般概念,以及他的 Macwire 函式庫提供的部分輔助工具。此方法很容易與 Play 提供的內建元件特徵整合。

在以下範例中,我們將示範如何使用內建元件輔助特徵手動連接 Play 應用程式。透過閱讀提供的元件特徵的原始程式碼,應該可以輕易地將此方法調整為其他依賴性注入技術。

§應用程式進入點

每個在 JVM 上執行的應用程式都需要一個透過反射載入的進入點,即使您的應用程式自行啟動,主類別仍然會透過反射載入,而且它的主方法會使用反射來尋找和呼叫。

在 Play 的開發模式中,Play 使用的 JVM 和 HTTP 伺服器必須在應用程式重新啟動之間持續執行。為了實作這一點,Play 提供了一個您可以實作的 ApplicationLoader 特質。每次重新載入應用程式時,都會建構並呼叫應用程式載入器,以載入應用程式。

此特質的載入方法將應用程式載入器 Context 作為引數,其中包含 Play 應用程式中所有在應用程式本身結束後仍存在的元件,且無法由應用程式本身建構。其中許多元件特別用於在開發模式中提供功能,例如,來源對應器允許 Play 錯誤處理常式呈現引發例外狀況的地方的來源程式碼。

最簡單的實作方式可以透過擴充 Play BuiltInComponentsFromContext 抽象類別來提供。此類別會取得 context,並根據該 context 提供所有內建元件。您只需要提供一個路由器,讓 Play 將要求路由到適當的位置。以下是使用 null 路由器,以這種方式建立的最簡單應用程式

import play.api._
import play.api.routing.Router
import play.api.ApplicationLoader.Context
import play.filters.HttpFiltersComponents

class MyApplicationLoader extends ApplicationLoader {
  def load(context: Context) = {
    new MyComponents(context).application
  }
}

class MyComponents(context: Context) extends BuiltInComponentsFromContext(context) with HttpFiltersComponents {
  lazy val router = Router.empty
}

若要設定 Play 使用此應用程式載入器,請設定 play.application.loader 屬性,使其指向 application.conf 檔案中的完全限定類別名稱

play.application.loader=MyApplicationLoader

此外,如果您正在修改使用內建 Guice 模組的現有專案,您應該可以從 build.sbt 中的 libraryDependencies 中移除 guice

§設定記錄

若要正確設定 Play 中的記錄,必須在傳回應用程式之前執行 LoggerConfigurator。預設的 BuiltInComponentsFromContext 不會為您呼叫 LoggerConfigurator

此初始化程式碼必須新增至您的應用程式載入器

class MyApplicationLoaderWithInitialization extends ApplicationLoader {
  def load(context: Context) = {
    LoggerConfigurator(context.environment.classLoader).foreach {
      _.configure(context.environment, context.initialConfiguration, Map.empty)
    }
    new MyComponents(context).application
  }
}

如果您從 Play 2.4.x 進行移轉,LoggerConfiguratorLogger.configure() 的替代方案,並且允許 自訂不同的記錄架構

§提供路由器

預設情況下,Play 會使用 注入式路由產生器。這會產生一個路由器,其建構函式會接受路由檔案中每個控制器和包含的路由器,順序與它們在路由檔案中出現的順序相同。路由器的建構函式也會在其第一個引數中接受 HttpErrorHandler,用於處理參數繫結錯誤,以及一個字串前綴作為其最後一個引數。還會提供一個將其預設為 "/" 的重載建構函式。

下列路由

GET        /                    controllers.Application.index
GET        /foo                 controllers.Application.foo
->         /bar                 bar.Routes
GET        /assets/*file        _root_.controllers.Assets.at(path = "/public", file)

將產生具有下列建構函式簽章的路由器

class Routes(
  override val errorHandler: play.api.http.HttpErrorHandler,
  Application_0: controllers.Application,
  bar_Routes_0: bar.Routes,
  Assets_1: controllers.Assets,
  val prefix: String
) extends GeneratedRouter {

  def this(
    errorHandler: play.api.http.HttpErrorHandler,
    Application_0: controllers.Application,
    bar_Routes_0: bar.Routes,
    Assets_1: controllers.Assets
  ) = this(Application_0, bar_Routes_0, Assets_1, "/")
  ...
}

請注意,參數的命名並非有意定義良好(事實上,附加到它們的索引是隨機的,取決於雜湊映射順序),因此您不應依賴這些參數的名稱。

要在實際應用程式中使用此路由器

import play.api._
import play.api.ApplicationLoader.Context
import play.filters.HttpFiltersComponents
import router.Routes

class MyApplicationLoader extends ApplicationLoader {
  def load(context: Context) = {
    new MyComponents(context).application
  }
}

class MyComponents(context: Context)
    extends BuiltInComponentsFromContext(context)
    with HttpFiltersComponents
    with controllers.AssetsComponents {
  lazy val barRoutes             = new bar.Routes(httpErrorHandler)
  lazy val applicationController = new controllers.Application(controllerComponents)

  lazy val router: Routes = new Routes(httpErrorHandler, applicationController, barRoutes, assets)
}

§使用其他元件

如前所述,Play 提供許多輔助特質,用於連接其他元件。例如,如果您想要使用訊息模組,您可以將 I18nComponents 混入您的元件 cake,如下所示

import play.api.i18n._

class MyComponents(context: Context)
    extends BuiltInComponentsFromContext(context)
    with I18nComponents
    with HttpFiltersComponents {
  lazy val router = Router.empty

  lazy val myComponent = new MyComponent(messagesApi)
}

class MyComponent(messages: MessagesApi) {
  // ...
}

其他輔助特質也可用,例如 CSRFComponentsAhcWSComponents

下一步:應用程式設定


在這個文件中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。閱讀完 文件指南 後,請隨時提交拉取請求。有問題或建議要分享嗎?前往 我們的社群論壇 與社群展開對話。