为什么在Play Framework中使用@Singleton而不是Scala的对象?

时间:2016-05-17 17:29:03

标签: scala object playframework guice

我已经将Play! Framework用于Scala近一年了。我目前正在使用版本2.5.x

我知道Play中控制器的发展以及开发人员如何被迫远离静态object路由。

我也知道游戏中的Guice用法。

如果您下载activator并运行:

activator new my-test-app play-scala

Activator将为您生成一个模板项目。 我的问题是关于该模板的this文件。

我测试应用程式/应用/服务/ Counter.scala

package services

import java.util.concurrent.atomic.AtomicInteger
import javax.inject._

/**
 * This trait demonstrates how to create a component that is injected
 * into a controller. The trait represents a counter that returns a
 * incremented number each time it is called.
 */
trait Counter {
  def nextCount(): Int
}

/**
 * This class is a concrete implementation of the [[Counter]] trait.
 * It is configured for Guice dependency injection in the [[Module]]
 * class.
 *
 * This class has a `Singleton` annotation because we need to make
 * sure we only use one counter per application. Without this
 * annotation we would get a new instance every time a [[Counter]] is
 * injected.
 */
@Singleton
class AtomicCounter extends Counter {  
  private val atomicCounter = new AtomicInteger()
  override def nextCount(): Int = atomicCounter.getAndIncrement()
}

您还可以在this文件中查看其用法:

我测试应用程式/应用/控制器/ CountController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._
import services.Counter

/**
 * This controller demonstrates how to use dependency injection to
 * bind a component into a controller class. The class creates an
 * `Action` that shows an incrementing count to users. The [[Counter]]
 * object is injected by the Guice dependency injection system.
 */
@Singleton
class CountController @Inject() (counter: Counter) extends Controller {

  /**
   * Create an action that responds with the [[Counter]]'s current
   * count. The result is plain text. This `Action` is mapped to
   * `GET /count` requests by an entry in the `routes` config file.
   */
  def count = Action { Ok(counter.nextCount().toString) }

}

这意味着每个具有@Inject() (counter: Counter)构造函数的控制器都将收到Counter的相同实例。

所以我的问题是:

为什么将@Singleton然后@Inject用于控制器,本示例中您可以使用Scala对象? 它的代码少得多。

示例:

我测试应用程式/应用/服务/ Counter.scala

package services

trait ACounter {
  def nextCount: Int
}

object Counter with ACounter {
  private val atomicCounter = new AtomicInteger()
  def nextCount(): Int = atomicCounter.getAndIncrement()
}

像这样使用它:

我测试应用程式/应用/控制器/ CountController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._

import services.{Counter, ACounter}

/**
 * This controller demonstrates how to use dependency injection to
 * bind a component into a controller class. The class creates an
 * `Action` that shows an incrementing count to users. The [[Counter]]
 * object is injected by the Guice dependency injection system.
 */
@Singleton
class CountController extends Controller {
  //depend on abstractions
  val counter: ACounter = Counter

  def count = Action { Ok(counter.nextCount().toString) }

}

有什么区别?注射是首选,为什么?

3 个答案:

答案 0 :(得分:18)

注射是首选方式吗?一般是的

使用依赖注入的一些优点:

  1. 将控制器与Counter的具体实现分离。
    • 如果您要使用object,则必须更改控制器以指向不同的实现。 EG Counter2.nextCount().toString
  2. 您可以在测试期间使用Guice自定义绑定更改实现
    • 让我们说Counter内部正在进行WS通话。这可能会导致单元测试的一些困难。如果您正在使用Guice的依赖注入,则可以覆盖CounterAtomicCounter之间的绑定,以指向您专门为测试编写的Counter的脱机版本。有关使用Guice for Play测试的详细信息,请参阅here
  3. 另请参阅Play用于迁移到DI的motivations

    我之所以这么说,是因为我已经看到使用Spring和其他Java框架的依赖注入非常错误。我说你应该使用自己的判断,但在使用DI进行游戏时会犯错。

答案 1 :(得分:6)

也许是因为Scala的单身对象无法获得参数?例如,如果您有一个注入了DAO的服务类,并且您想在控制器中使用服务,则必须注入它。最简单的方法(IMO)是DI with Guice ...此外,您可以将您的依赖项放在一个地方(模块)等...

答案 2 :(得分:4)

我不确定,如果我理解你的问题,但注射是首选,因为:

  • 应用程序的不同部分不那么耦合
  • 使用提供相同功能的不同类替换您的依赖项更容易(如果您将来需要这样做) - 您将需要更改几行代码而不是查找所有对象的出现
  • 测试更简单(特别是当你需要模拟某些东西时)

简短地说:来自SOLID原则的D:“取决于抽象。不要依赖于结核”。