§编译时依赖注入
Play 默认提供了一种运行时依赖注入机制,即直到运行时才连接依赖关系的依赖注入。这种方法既有优点也有缺点,主要优点是减少了样板代码,主要缺点是应用程序的构建在编译时没有得到验证。
另一种方法是使用编译时依赖注入。最简单的方法是手动构建和连接依赖关系。还存在其他更高级的技术和工具,例如 Dagger。所有这些都可以轻松地在构造函数和手动连接之上实现,因此 Play 对编译时依赖注入的支持是通过提供公共构造函数和工厂方法作为 API 来提供的。
注意:如果您不熟悉编译时 DI 或一般的 DI,那么值得阅读 Adam Warski 的 Scala 中的 DI 指南,该指南讨论了编译时 DI 的一般概念。虽然这是针对 Scala 开发者的解释,但它也可以让您了解编译时注入的优势。
除了提供公共构造函数和工厂方法之外,Play 的所有开箱即用模块都提供了一些接口,这些接口实现了轻量级的蛋糕模式,以方便使用。这些接口建立在公共构造函数之上,完全是可选的。在某些应用程序中,它们可能不适合使用,但在许多应用程序中,它们将是连接 Play 提供的组件的非常方便的机制。这些接口遵循以 Components 结尾的特征名称命名约定,例如,DB API 的基于 HikariCP 的默认实现提供了一个名为 HikariCPComponents 的接口。
注意:当然,Java 在完全实现蛋糕模式方面有一些限制。例如,您不能在接口中拥有状态。
在下面的示例中,我们将展示如何使用内置组件帮助器接口手动连接 Play 应用程序。通过阅读提供的组件接口的源代码,将此方法调整为其他依赖注入技术应该很简单。
§应用程序入口点
每个在 JVM 上运行的应用程序都需要一个通过反射加载的入口点 - 即使您的应用程序自行启动,主类仍然通过反射加载,并且其主方法使用反射定位并调用。
在 Play 的开发模式下,Play 使用的 JVM 和 HTTP 服务器必须在应用程序重启之间保持运行。为了实现这一点,Play 提供了一个 ApplicationLoader 接口,您可以实现它。应用程序加载器在每次应用程序重新加载时都会被构造和调用,以加载应用程序。
此接口的 load 方法将应用程序加载器 Context 作为参数,其中包含 Play 应用程序所需的所有组件,这些组件比应用程序本身更长寿,并且不能由应用程序本身构造。其中一些组件专门用于在开发模式下提供功能,例如,源代码映射器允许 Play 错误处理程序呈现抛出异常的位置的源代码。
最简单的实现可以通过扩展 Play BuiltInComponentsFromContext 抽象类来提供。此类接受上下文,并基于该上下文提供所有内置组件。您只需要提供一个路由器,以便 Play 将请求路由到它。以下是使用空路由器以这种方式创建的最简单的应用程序
import play.Application;
import play.ApplicationLoader;
import play.BuiltInComponentsFromContext;
import play.LoggerConfigurator;
import play.controllers.AssetsComponents;
import play.db.ConnectionPool;
import play.db.HikariCPComponents;
import play.filters.components.HttpFiltersComponents;
import play.mvc.Results;
import play.routing.Router;
import play.routing.RoutingDslComponentsFromContext;
public class MyComponents extends BuiltInComponentsFromContext implements HttpFiltersComponents {
public MyComponents(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
return Router.empty();
}
}
然后是应用程序加载器
public class MyApplicationLoader implements ApplicationLoader {
@Override
public Application load(Context context) {
return new MyComponents(context).application();
}
}
要配置 Play 使用此应用程序加载器,请将 play.application.loader 属性配置为指向 application.conf 文件中的完全限定类名
play.application.loader=MyApplicationLoader
此外,如果您正在修改使用内置 Guice 模块的现有项目,您应该能够从 build.sbt 中的 libraryDependencies 中删除 guice。
§提供路由器
要配置路由器,您有两个选择,使用 RoutingDsl 或生成的路由器。
§使用 RoutingDsl 提供路由器
为了使这更容易,Play 有一个 RoutingDslComponentsFromContext 类,它已经提供了一个 RoutingDsl 实例,该实例使用其他提供的组件创建
public class MyComponentsWithRouter extends RoutingDslComponentsFromContext
implements HttpFiltersComponents {
public MyComponentsWithRouter(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
// routingDsl method is provided by RoutingDslComponentsFromContext
return routingDsl().GET("/path").routingTo(request -> Results.ok("The content")).build();
}
}§使用生成的路由器
默认情况下,Play 将使用 注入的路由生成器。这将生成一个路由器,其构造函数接受来自路由文件中的每个控制器和包含的路由器,顺序与它们在路由文件中出现的顺序相同。路由器的构造函数还将以 play.api.http.HttpErrorHandler(play.http.HttpErrorHandler 的 Scala 版本)作为第一个参数,用于处理参数绑定错误,以及一个前缀字符串作为最后一个参数。还将提供一个重载的构造函数,该构造函数将此默认设置为 "/"。
以下路由
GET / controllers.HomeController.index
GET /assets/*file controllers.Assets.at(path = "/public", file)
将生成一个路由器,该路由器接受 controllers.HomeController、controllers.Assets 以及任何其他具有声明路由的实例。要在实际应用程序中使用此路由器
public class MyComponentsWithGeneratedRouter extends BuiltInComponentsFromContext
implements HttpFiltersComponents, AssetsComponents {
public MyComponentsWithGeneratedRouter(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
HomeController homeController = new HomeController();
Assets assets =
new Assets(scalaHttpErrorHandler(), assetsMetadata(), environment().asScala());
return new router.Routes(scalaHttpErrorHandler(), homeController,
return new javaguide.dependencyinjection.Routes(
scalaHttpErrorHandler(), homeController, assets)
.asJava();
}
}§配置日志记录
为了在 Play 中正确配置日志记录,必须在返回应用程序之前运行 LoggerConfigurator。默认的 BuiltInComponentsFromContext 不会为您调用 LoggerConfigurator。
此初始化代码必须添加到您的应用程序加载器中
import scala.jdk.javaapi.OptionConverters;
public class MyAppLoaderWithLoggerConfiguration implements ApplicationLoader {
@Override
public Application load(Context context) {
LoggerConfigurator.apply(context.environment().classLoader())
.ifPresent(
loggerConfigurator ->
loggerConfigurator.configure(context.environment(), context.initialConfig()));
return new MyComponents(context).application();
}
}§使用其他组件
如前所述,Play 提供了许多用于连接其他组件的辅助特征。例如,如果您想使用数据库连接池,您可以将 HikariCPComponents 混合到您的组件蛋糕中,如下所示
public class MyComponentsWithDatabase extends BuiltInComponentsFromContext
implements HikariCPComponents, HttpFiltersComponents {
public MyComponentsWithDatabase(ApplicationLoader.Context context) {
super(context);
}
@Override
public Router router() {
return Router.empty();
}
public SomeComponent someComponent() {
// connectionPool method is provided by HikariCPComponents
return new SomeComponent(connectionPool());
}
}
其他辅助特征也可用,例如 CSRFComponents 或 AhcWSComponents。提供组件的 Java 接口的完整列表是
play.BuiltInComponentsplay.components.PekkoComponentsplay.components.ApplicationComponentsplay.components.BaseComponentsplay.components.BodyParserComponentsplay.components.ConfigurationComponentsplay.components.CryptoComponentsplay.components.FileMimeTypesComponentsplay.components.HttpComponentsplay.components.HttpConfigurationComponentsplay.components.HttpErrorHandlerComponentsplay.components.TemporaryFileComponentsplay.controllers.AssetsComponentsplay.i18n.I18nComponentsplay.libs.ws.ahc.AhcWSComponentsplay.libs.ws.ahc.WSClientComponentsplay.cache.ehcache.EhCacheComponentsplay.filters.components.AllowedHostsComponentsplay.filters.components.CORSComponentsplay.filters.components.CSRFComponentsplay.filters.components.GzipFilterComponentsplay.filters.components.HttpFiltersComponentsplay.filters.components.NoHttpFiltersComponentsplay.filters.components.RedirectHttpsComponentsplay.filters.components.SecurityHeadersComponentsplay.routing.RoutingDslComponentsplay.data.FormFactoryComponentsplay.data.validation.ValidatorsComponentsplay.db.ConnectionPoolComponentsplay.db.DBComponentsplay.db.HikariCPComponentsplay.db.jpa.JPAComponentsplay.libs.openid.OpenIdComponents
下一步:应用程序设置
发现文档中的错误? 此页面的源代码可以在 此处 找到。 阅读完 文档指南 后,请随时贡献拉取请求。 有问题或建议要分享? 请访问 我们的社区论坛 与社区进行交流。