我正在尝试从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之类的东西(不使用弃用的方法)?
答案 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
,这里有两个选项:
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 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 {
// ...
}