【问题标题】:Dependency injection with abstract class and object in Play Framework 2.5Play Framework 2.5 中抽象类和对象的依赖注入
【发布时间】:2016-06-19 10:32:07
【问题描述】:

我正在尝试从 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 WSto 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) { /* ... */ }

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

有什么想法可以让事情变得如此复杂吗?

【问题讨论】:

    标签: scala playframework playframework-2.5


    【解决方案1】:

    这与 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) {
        // ...
    }
    

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

    1. 使用default execution context,即play.api.libs.concurrent.Execution.Implicits.defaultContext
    2. 使用other thread pools

    这取决于您,但您可以轻松地注入 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 
        }
    }
    

    【讨论】:

    • 非常感谢您的详细回答,但我仍在努力解决这个问题。首先,class HelloWorldService 从哪里得到这两个参数?他们不应该在这里注入吗,因此class HelloWorldService Inject() (configuration: Configuration, ws: WSClient)?当HelloWorldService 是一个对象时,我可以简单地调用它的方法,例如HelloWorldService.functionX。不,它是一个 (@Singleton 带注释的) 类,我现在如何调用它的方法?我一直在寻找岁月。我必须先用new 创建一个实例吗?然后我需要 args...
    • 我想我现在可以自己回答我的最后一条评论了。无论我想在哪里使用 HelloWorldService,我都会将它作为注入依赖项添加到那里的构造函数中,例如在控制器中:class Application @Inject() (helloWorldService: HelloWorldService)(implicit context: ExecutionContext) extends Controller {}。最后一件事:您的 play.api.libs.concurrent.Execution.Implicits.defaultContext 工作正常,但我在 Play 示例中看到越来越多的上下文也被注入,就像在此评论中的控制器示例中一样。这实际上是一样的吗?
    【解决方案2】:

    在 Scala 中,

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-07-15
      • 2021-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多