§编译时依赖注入
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.BuiltInComponents
play.components.PekkoComponents
play.components.ApplicationComponents
play.components.BaseComponents
play.components.BodyParserComponents
play.components.ConfigurationComponents
play.components.CryptoComponents
play.components.FileMimeTypesComponents
play.components.HttpComponents
play.components.HttpConfigurationComponents
play.components.HttpErrorHandlerComponents
play.components.TemporaryFileComponents
play.controllers.AssetsComponents
play.i18n.I18nComponents
play.libs.ws.ahc.AhcWSComponents
play.libs.ws.ahc.WSClientComponents
play.cache.ehcache.EhCacheComponents
play.filters.components.AllowedHostsComponents
play.filters.components.CORSComponents
play.filters.components.CSRFComponents
play.filters.components.GzipFilterComponents
play.filters.components.HttpFiltersComponents
play.filters.components.NoHttpFiltersComponents
play.filters.components.RedirectHttpsComponents
play.filters.components.SecurityHeadersComponents
play.routing.RoutingDslComponents
play.data.FormFactoryComponents
play.data.validation.ValidatorsComponents
play.db.ConnectionPoolComponents
play.db.DBComponents
play.db.HikariCPComponents
play.db.jpa.JPAComponents
play.libs.openid.OpenIdComponents
下一步:应用程序设置
发现文档中的错误? 此页面的源代码可以在 此处 找到。 阅读完 文档指南 后,请随时贡献拉取请求。 有问题或建议要分享? 请访问 我们的社区论坛 与社区进行交流。