Play框架中的Scala依赖注入

时间:2017-03-18 16:07:46

标签: scala dependency-injection playframework

我是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(())
  }
}

1 个答案:

答案 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个模块将自动使用它。