我是Scala,PlayFramework和Dependency Injection的新手。我下载了示例scala play框架代码。有人可以向我解释为什么我们需要注入Clock和appLifeCycle吗?它在上面引用,所以没有必要注入它吗?这里发生了什么?为什么我们一般需要为Web框架执行此操作?
package services
import java.time.{Clock, Instant}
import javax.inject._
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future
/**
* This class demonstrates how to run code when the
* application starts and stops. It starts a timer when the
* application starts. When the application stops it prints out how
* long the application was running for.
*
* This class is registered for Guice dependency injection in the
* [[Module]] class. We want the class to start when the application
* starts, so it is registered as an "eager singleton". See the code
* in the [[Module]] class to see how this happens.
*
* This class needs to run code when the server stops. It uses the
* application's [[ApplicationLifecycle]] to register a stop hook.
*/
@Singleton
class ApplicationTimer @Inject() (clock: Clock, appLifecycle: ApplicationLifecycle) {
// This code is called when the application starts.
private val start: Instant = clock.instant
Logger.info(s"ApplicationTimer demo: Starting application at $start.")
// When the application starts, register a stop hook with the
// ApplicationLifecycle object. The code inside the stop hook will
// be run when the application stops.
appLifecycle.addStopHook { () =>
val stop: Instant = clock.instant
val runningTime: Long = stop.getEpochSecond - start.getEpochSecond
Logger.info(s"ApplicationTimer demo: Stopping application at ${clock.instant} after ${runningTime}s.")
Future.successful(())
}
}
答案 0 :(得分:2)
我假设您正在使用Lightbend的Play Scala Seed,其中包含您发布的代码示例。
如果您查看java.time.Clock的文档,您会注意到它(强调我的):
应用程序的最佳实践是将时钟传递到任何需要当前瞬间的方法。依赖注入框架是实现此目的的一种方法。 {..代码示例省略..}此方法允许在测试期间使用备用时钟,例如固定或偏移。
最终,依赖注入的目的是允许您定义要注入类或对象的接口,并在一个位置配置该接口的实现。另一种方法是必须更新多个文件中的硬编码依赖项,这可能很麻烦且容易出错。在Play Scala Seed项目中,您会注意到一个名为app/Module.scala
的文件。此文件是您可以配置绑定的位置,它们将在应用程序启动时自动绑定。请注意我们绑定Clock
实现的行:
class Module extends AbstractModule {
override def configure() = {
// Use the system clock as the default implementation of Clock
bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
// Ask Guice to create an instance of ApplicationTimer when the
// application starts.
bind(classOf[ApplicationTimer]).asEagerSingleton()
// Set AtomicCounter as the implementation for Counter.
bind(classOf[Counter]).to(classOf[AtomicCounter])
}
}
此配置说“当我的应用程序启动时,无论我在哪里注入Clock
都应使用Clock.systemDefaultZone
。”如果您希望ApplicationTimer
在测试期间使用不同的时钟,您可能会执行以下操作:
import play.api.{Environment, Mode}
// Notice that we include the environment
class Module(environment: Environment) extends AbstractModule {
override def configure() = {
// Use the system clock as the default implementation of Clock
environment.mode match {
case Mode.Prod | Mode.Dev => {
bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
}
case Mode.Test => {
// Specifically use UTC clock in tests, because maybe it's easier to write assertions that way
// You could inject anything here and the ApplicationTimer would use it during tests
bind(classOf[Clock]).toInstance(Clock.systemUTC())
}
}
bind(classOf[ApplicationTimer]).asEagerSingleton()
bind(classOf[Counter]).to(classOf[AtomicCounter])
}
}
您可以在根包中的其他位置定义模块(即文件顶部没有package com.example.whatever
声明),它们也会自动加载。否则,您需要在conf/application.conf
中添加一个绑定,将模块的名称添加到play.modules.enabled
键。您可以在Play Scala Seed中看到一个已注释掉的示例。我还在我写的另一个答案here中深入探讨。
对于ApplicationLifecycle
,这是Play提供的一个特殊模块,您可以覆盖您自己的绑定,但我不确定您为什么要这样做。它可以让您访问在应用程序关闭之前执行的挂钩。同样,注入此组件是因为将其交换出来很简单。想象一下,有100个模块都依赖于应用程序生命周期。默认情况下,它绑定到DefaultApplicationLifecycle
(您可以看到它被绑定here)。如果您在所有100个模块中都有硬编码DefaultApplicationLifecycle
,那么如果您想切换到不同的生命周期,则必须更新每个模块。使用依赖注入,您只需要配置绑定以使用不同的生命周期,100个模块将自动使用它。