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

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

标签: 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

但现在你should use dependency injection to use WS以及to use access the current Play application

我有这样的事情(缩短):

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之类的东西(不使用弃用的方法)?

2 个答案:

答案 0 :(得分:14)

这与Guice如何处理继承更为相关,如果你没有使用Guice,你必须做的就是你要做的事情,Guice是在超类中声明参数并在子类中调用超级构造函数。 Guice甚至在its docs建议:

  

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

     

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

     
      
  • 子类必须使用所有依赖项调用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;
  }
}

依赖注入不会改变它,你只需要添加注释来指示如何注入依赖项。在这种情况下,由于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;
  }
}

翻译成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. 使用default execution contextplay.api.libs.concurrent.Execution.Implicits.defaultContext
  2. 使用other thread pools
  3. 这取决于您,但您可以轻松注入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 docs对此有一个很好的解释:

      

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

    在我们的案例中,对于HelloWorldService,我们使用constructor injection来启用Guice来设置/创建对象图。

    如何注入HelloWorldService

    就像WSClient有一个模块来描述如何将实现绑定到接口/特征时,我们可以对HelloWorldService执行相同的操作。 Play docs有关于如何创建和配置模块的明确说明,因此我不在此重复。

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

    class MyController @Inject() (service: Microservice) extends Controller {
    
        def index = Action {
            // access "service" here and do whatever you want 
        }
    }
    

答案 1 :(得分:1)

在scala中,

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

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

case class Child @Inject() (param1: Int,
                            depOne: DependencyOne,
                            depTwo: DependencyTwo) extends Base {
  // ...
}