这与 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,这里我们有两个选择:
- 使用default execution context,即
play.api.libs.concurrent.Execution.Implicits.defaultContext
- 使用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 为WSClient 和Configuration 声明模块。这两个模块都为 Guice 提供了有关如何构建这些依赖项的足够信息,并且还有一些模块描述了如何构建 WSClient 和 Configuration 所需的依赖项。同样,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
}
}