文档

§编译时依赖注入

Play 默认提供了一种运行时依赖注入机制,即直到运行时才连接依赖项的依赖注入。这种方法既有优点也有缺点,主要优点是减少了样板代码,主要缺点是应用程序的构建在编译时没有得到验证。

Scala 开发中流行的另一种方法是使用编译时依赖注入。最简单的情况下,编译时 DI 可以通过手动构建和连接依赖项来实现。还存在其他更高级的技术和工具,例如基于宏的自动连接工具、隐式自动连接技术以及各种形式的蛋糕模式。所有这些都可以轻松地基于构造函数和手动连接来实现,因此 Play 对编译时依赖注入的支持是通过提供公共构造函数和工厂方法作为 API 来实现的。

除了提供公共构造函数和工厂方法之外,Play 的所有开箱即用模块都提供了一些实现轻量级蛋糕模式的特征,以方便使用。这些特征建立在公共构造函数之上,完全是可选的。在某些应用程序中,它们可能不适合使用,但在许多应用程序中,它们将是连接 Play 提供的组件的非常方便的机制。这些特征遵循以 Components 结尾的特征名称命名约定,例如,DB API 的基于 HikariCP 的默认实现提供了一个名为 HikariCPComponents 的特征。

如果您不熟悉编译时 DI 或一般的 DI,那么值得阅读 Adam Warski 的 Scala 中的 DI 指南,该指南讨论了编译时 DI 的一般情况以及他的 Macwire 库提供的一些帮助程序。这种方法很容易与 Play 提供的内置组件特征集成。

在下面的示例中,我们将展示如何使用内置的组件帮助程序特征手动连接 Play 应用程序。通过阅读提供的组件特征的源代码,应该很容易将此方法调整为其他依赖注入技术。

§应用程序入口点

在 JVM 上运行的每个应用程序都需要一个由反射加载的入口点 - 即使您的应用程序自行启动,主类仍然由反射加载,并且其 main 方法使用反射定位并调用。

在 Play 的开发模式下,Play 使用的 JVM 和 HTTP 服务器必须在应用程序重启之间保持运行。为了实现这一点,Play 提供了一个 ApplicationLoader 特性,您可以实现它。应用程序加载器在每次应用程序重新加载时被构造和调用,以加载应用程序。

此特性的 load 方法以应用程序加载器 Context 作为参数,其中包含 Play 应用程序所需的所有组件,这些组件比应用程序本身更长寿,并且不能由应用程序本身构造。其中一些组件专门用于在开发模式下提供功能,例如,源代码映射器允许 Play 错误处理程序呈现抛出异常的位置的源代码。

最简单的实现可以通过扩展 Play BuiltInComponentsFromContext 抽象类来提供。此类接受上下文,并根据该上下文提供所有内置组件。您只需要提供一个路由器,以便 Play 将请求路由到它。以下是使用空路由器以这种方式创建的最简单的应用程序

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 使用此应用程序加载器,请在 application.conf 文件中将 play.application.loader 属性配置为指向完全限定的类名

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 混合到您的组件蛋糕中,如下所示

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

下一步:应用程序设置


在本文档中发现错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。