这与 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;
}
}
依赖注入不会改变这一点,您只需添加注释以指示如何注入依赖项。在这种情况下,由于Base
class 是abstract
,Base
因此无法创建 的实例,我们可以跳过它并仅对Child
class 进行注释:
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
,这里我们有两个选择:
- 使用默认的执行上下文,这将是
play.api.libs.concurrent.Execution.Implicits.defaultContext
- 使用其他线程池
这取决于您,但您可以轻松地注入一个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 文档对此有很好的解释:
通过依赖注入,对象在其构造函数中接受依赖。要构造一个对象,首先要构建它的依赖关系。但是要构建每个依赖项,您需要它的依赖项,依此类推。所以当你构建一个对象时,你确实需要构建一个对象图。
在我们的例子中,对于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
}
}