17

我正在尝试从 Play 2.4 迁移到 2.5 以避免过时的东西。

我有一个abstract class Microservice从中创建了一些对象。该类的一些函数Microservice用于play.api.libs.ws.WS发出 HTTP 请求并play.Play.application.configuration读取配置。

以前,我需要的只是一些导入,例如:

import play.api.libs.ws._
import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits.defaultContext

但是现在您应该使用依赖注入来使用WS访问当前的 Play 应用程序

我有这样的东西(缩短):

abstract class Microservice(serviceName: String) {
    // ...
    protected lazy val serviceURL: String = play.Play.application.configuration.getString(s"microservice.$serviceName.url")
    // ...and functions using WS.url()...
}

一个对象看起来像这样(缩短):

object HelloWorldService extends Microservice("helloWorld") {
    // ...
}

不幸的是,我不明白如何将所有东西(WS、配置、ExecutionContect)放入抽象类以使其工作。

我试图将其更改为:

abstract class Microservice @Inject() (serviceName: String, ws: WSClient, configuration: play.api.Configuration)(implicit context: scala.concurrent.ExecutionContext) {
    // ...
}

但这并不能解决问题,因为现在我也必须更改对象,但我不知道如何。

我试图把它object变成一个@Singleton class,比如:

@Singleton
class HelloWorldService @Inject() (implicit ec: scala.concurrent.ExecutionContext) extends Microservice ("helloWorld", ws: WSClient, configuration: play.api.Configuration) { /* ... */ }

我尝试了各种组合,但我没有得到任何结果,我觉得我在这里并没有真正走在正确的轨道上。

有什么想法可以让我以正确的方式使用 WS 之类的东西(不使用不推荐使用的方法)而不会使事情变得如此复杂吗?

4

2 回答 2

16

这与 Guice 处理继承的方式更相关,如果您不使用 Guice,您必须完全按照自己的方式进行操作,即向超类声明参数并在您的子类中调用超级构造函数。Guice 甚至在其文档中建议它:

尽可能使用构造函数注入来创建不可变对象。不可变对象简单、可共享且可以组合。

构造函数注入有一些限制:

  • 子类必须使用所有依赖项调用 super()。这使得构造函数注入很麻烦,尤其是当注入的基类发生变化时。

在纯 Java 中,这意味着做这样的事情:

public abstract class Base {

  private final Dependency dep;

  public Base(Dependency dep) {
    this.dep = dep;
  }
}

public class Child extends Base {

  private final AnotherDependency anotherDep;

  public Child(Dependency dep, AnotherDependency anotherDep) {
    super(dep); // guaranteeing that fields at superclass will be properly configured
    this.anotherDep = anotherDep;
  }
}

依赖注入不会改变这一点,您只需添加注释以指示如何注入依赖项。在这种情况下,由于Baseclass 是abstractBase因此无法创建 的实例,我们可以跳过它并仅对Childclass 进行注释:

public abstract class Base {

  private final Dependency dep;

  public Base(Dependency dep) {
    this.dep = dep;
  }
}

public class Child extends Base {

  private final AnotherDependency anotherDep;

  @Inject
  public Child(Dependency dep, AnotherDependency anotherDep) {
    super(dep); // guaranteeing that fields at superclass will be properly configured
    this.anotherDep = anotherDep;
  }
}

翻译成Scala,我们会得到这样的东西:

abstract class Base(dep: Dependency) {
  // something else
}

class Child @Inject() (anotherDep: AnotherDependency, dep: Dependency) extends Base(dep) {
  // something else
}

现在,我们可以重写您的代码以使用这些知识并避免不推荐使用的 API:

abstract class Microservice(serviceName: String, configuration: Configuration, ws: WSClient) {
    protected lazy val serviceURL: String = configuration.getString(s"microservice.$serviceName.url")
    // ...and functions using the injected WSClient...
}

// a class instead of an object
// annotated as a Singleton
@Singleton
class HelloWorldService(configuration: Configuration, ws: WSClient)
    extends Microservice("helloWorld", configuration, ws) {
    // ...
}

最后一点是implicit ExecutionContext,这里我们有两个选择:

  1. 使用默认的执行上下文,这将是play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. 使用其他线程池

这取决于您,但您可以轻松地注入一个ActorSystem来查找调度程序。如果您决定使用自定义线程池,则可以执行以下操作:

abstract class Microservice(serviceName: String, configuration: Configuration, ws: WSClient, actorSystem: ActorSystem) {

    // this will be available here and at the subclass too
    implicit val executionContext = actorSystem.dispatchers.lookup("my-context")

    protected lazy val serviceURL: String = configuration.getString(s"microservice.$serviceName.url")
    // ...and functions using the injected WSClient...
}

// a class instead of an object
// annotated as a Singleton
@Singleton
class HelloWorldService(configuration: Configuration, ws: WSClient, actorSystem: ActorSystem)
    extends Microservice("helloWorld", configuration, ws, actorSystem) {
    // ...
}

如何使用HelloWorldService

现在,您需要了解两件事才能正确地HelloWorldService在您需要的地方注入一个实例。

从哪里HelloWorldService得到它的依赖?

Guice docs对此有很好的解释:

依赖注入

和工厂一样,依赖注入只是一种设计模式。核心原则是将行为与依赖解析分开。

依赖注入模式导致代码模块化且可测试,而 Guice 使其易于编写。要使用 Guice,我们首先需要告诉它如何将我们的接口映射到它们的实现。此配置在 Guice 模块中完成,该模块是任何实现 Module 接口的 Java 类。

然后,Playframework 为WSClientConfiguration声明模块。这两个模块都为 Guice 提供了有关如何构建这些依赖项的足够信息,并且还有一些模块描述了如何构建 和 所需的依赖WSClientConfiguration。同样,Guice 文档对此有很好的解释:

通过依赖注入,对象在其构造函数中接受依赖。要构造一个对象,首先要构建它的依赖关系。但是要构建每个依赖项,您需要它的依赖项,依此类推。所以当你构建一个对象时,你确实需要构建一个对象图。

在我们的例子中,对于HelloWorldService,我们使用构造函数注入来使 Guice 能够设置/创建我们的对象图。

是如何HelloWorldService注入的?

就像WSClient有一个模块来描述实现如何绑定到接口/特征一样,我们可以对HelloWorldService. Play docs对如何创建和配置模块有明确的解释,这里不再赘述。

但是在创建一个模块之后,要将 a 注入HelloWorldService到您的控制器中,您只需将其声明为依赖项:

class MyController @Inject() (service: Microservice) extends Controller {

    def index = Action {
        // access "service" here and do whatever you want 
    }
}
于 2016-03-06T04:24:55.510 回答
1

在斯卡拉,

-> 如果您不想将所有注入的参数显式转发给基本构造函数,您可以这样做:

abstract class Base {
  val depOne: DependencyOne
  val depTwo: DependencyTwo
  // ...
}

case class Child @Inject() (param1: Int,
                            depOne: DependencyOne,
                            depTwo: DependencyTwo) extends Base {
  // ...
}
于 2017-04-13T14:33:23.787 回答