在Play Framework 2.5中使用抽象类和对象进行依赖注入

Nic*_*ick 17 scala playframework playframework-2.5

我正在尝试从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
Run Code Online (Sandbox Code Playgroud)

但是现在你应该使用依赖注入来使用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()...
}
Run Code Online (Sandbox Code Playgroud)

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

object HelloWorldService extends Microservice("helloWorld") {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

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

我试着把它改成:

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

但这并没有解决问题,因为现在我也要改变对象,我无法弄清楚如何.

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

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

我尝试了各种各样的组合,但我没有到达任何地方,我觉得我在这里并没有真正走上正轨.

任何想法如何在不使事情如此复杂的情况下以正确的方式(不使用弃用的方法)使用WS之类的东西?

mar*_*ira 16

这与Guice如何处理继承更为相关,如果你没有使用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;
  }
}
Run Code Online (Sandbox Code Playgroud)

依赖注入不会改变它,您只需添加注释以指示如何注入依赖项.在这种情况下,由于Base类是abstract,然后没有Base可以创建的实例,我们可以跳过它,只是注释Child类:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

翻译成Scala,我们会有这样的事情:

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

class Child @Inject() (anotherDep: AnotherDependency, dep: Dependency) extends Base(dep) {
  // something else
}
Run Code Online (Sandbox Code Playgroud)

现在,我们可以重写您的代码以使用这些知识并避免弃用的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) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

最后一点是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) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

怎么用HelloWorldService?

现在,您需要了解两件事,以便正确地注入HelloWorldService您需要的实例.

从哪里HelloWorldService得到它的依赖?

Guice文档对此有一个很好的解释:

依赖注入

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

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

然后,Playframework为WSClient和Configuration声明模块.这两个模块都提供了关于如何建立这些依赖吉斯足够的信息,并且有模块描述如何建立必要的依赖WSClient和Configuration.同样,Guice文档对此有一个很好的解释:

通过依赖注入,对象接受其构造函数中的依赖项.要构造对象,首先要构建其依赖项.但是要构建每个依赖项,您需要它的依赖项,依此类推.因此,在构建对象时,您确实需要构建对象图.

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

如何HelloWorldService注射?

就像WSClient有一个模块来描述一个实现如何绑定到接口/特性,我们可以做同样的事情HelloWorldService.播放文档有关于如何创建和配置模块的明确说明,所以我在此不再重复.

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

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

    def index = Action {
        // access "service" here and do whatever you want 
    }
}
Run Code Online (Sandbox Code Playgroud)