§编译时依赖注入
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 迁移,LoggerConfigurator
是 Logger.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) {
// ...
}
其他辅助特征也可用,例如 CSRFComponents 或 AhcWSComponents
下一步:应用程序设置
在本文档中发现错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区开始对话。