Play Framework 2.5 中抽象类和对象的依赖注入 [英] Dependency injection with abstract class and object in Play Framework 2.5

查看:22
本文介绍了Play Framework 2.5 中抽象类和对象的依赖注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试从 Play 2.4 迁移到 2.5,以避免弃用.

I'm trying to migrate from Play 2.4 to 2.5 avoiding deprecated stuff.

我有一个抽象类微服务,我从中创建了一些对象.Microservice 类的一些函数使用了 play.api.libs.ws.WS 来发出 HTTP 请求,还有 play.Play.application.configuration读取配置.

I had an abstract class Microservice from which I created some objects. Some functions of the Microservice class used play.api.libs.ws.WS to make HTTP requests and also play.Play.application.configuration to read the configuration.

以前,我只需要一些导入,例如:

Previously, all I needed was some imports like:

import play.api.libs.ws._
import play.api.Play.current
import play.api.libs.concurrent.Execution.Implicits.defaultContext

但是现在你应该使用依赖注入来使用WS并且使用访问当前播放应用程序.

我有这样的东西(缩短):

I have something like this (shortened):

abstract class Microservice(serviceName: String) {
    // ...
    protected lazy val serviceURL: String = play.Play.application.configuration.getString(s"microservice.$serviceName.url")
    // ...and functions using WS.url()...
}

一个对象看起来像这样(缩短):

An object looks something like this (shortened):

object HelloWorldService extends Microservice("helloWorld") {
    // ...
}

不幸的是,我不明白我是如何将所有东西(WS、配置、ExecutionContect)放入抽象类以使其工作的.

Unfortunately I don't understand how I get all the stuff (WS, configuration, ExecutionContect) into the abstract class to make it work.

我尝试将其更改为:

abstract class Microservice @Inject() (serviceName: String, ws: WSClient, configuration: play.api.Configuration)(implicit context: scala.concurrent.ExecutionContext) {
    // ...
}

但这并不能解决问题,因为现在我也必须更改对象,而且我不知道如何更改.

But this doesn't solve the problem, because now I have to change the object too, and I can't figure out how.

我试图将 object 变成一个 @Singleton 类,例如:

I tried to turn the object into a @Singleton class, like:

@Singleton
class HelloWorldService @Inject() (implicit ec: scala.concurrent.ExecutionContext) extends Microservice ("helloWorld", ws: WSClient, configuration: play.api.Configuration) { /* ... */ }

我尝试了各种组合,但我一无所获,而且我觉得我在这里并没有走在正确的轨道上.

I tried all sorts of combinations, but I'm not getting anywhere and I feel I'm not really on the right track here.

有什么想法可以让我以正确的方式使用 WS 之类的东西(不使用不推荐使用的方法)而不会使事情变得如此复杂?

Any ideas how I can use things like WS the proper way (not using deprecated methods) without making things so complicated?

推荐答案

这与 Guice 处理继承的方式更相关,您必须完全按照不使用 Guice 时的操作,即向超类并在您的子类中调用超构造函数.Guice 甚至在其文档中提出了建议:

This is more related to how Guice handles inheritance and you have to do exactly what you would do if you were not using Guice, which is declaring the parameters to the superclass and calling the super constructor at your child classes. Guice even suggest it at its docs:

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

Wherever possible, use constructor injection to create immutable objects. Immutable objects are simple, shareable, and can be composed.

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

Constructor injection has some limitations:

  • 子类必须使用所有依赖项调用 super().这使得构造函数注入变得很麻烦,尤其是当注入的基类发生变化时.
  • Subclasses must call super() with all dependencies. This makes constructor injection cumbersome, especially as the injected base class changes.

在纯 Java 中,这意味着做这样的事情:

In pure Java, it will means doing something like this:

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的实例,我们可以跳过它,只注释类:

Dependency injection won't change that and you will just have to add the annotations to indicate how to inject the dependencies. In this case, since Base class is abstract, and then no instances of Base can be created, we may skip it and just annotate 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,我们将得到如下内容:

Translating to Scala, we will have something like this:

abstract class Base(dep: Dependency) {
  // something else
}

class Child @Inject() (anotherDep: AnotherDependency, dep: Dependency) extends Base(dep) {
  // something else
}

现在,我们可以重写您的代码以使用这些知识并避免弃用 API:

Now, we can rewrite your code to use this knowledge and avoid deprecated APIs:

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 这里我们有两个选项:

The last point is the implicit ExecutionContext and here we have two options:

  1. 使用默认执行上下文,这将是play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. 使用其他线程池

这取决于您,但您可以轻松注入 ActorSystem 来查找调度程序.如果您决定使用自定义线程池,您可以执行以下操作:

This depends on you, but you can easily inject an ActorSystem to lookup the dispatcher. If you decide to go with a custom thread pool, you can do something like this:

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 的实例,您需要了解两件事.

How to use HelloWorldService?

Now, there are two things you need to understand in order to proper inject an instance of HelloWorldService where you need it.

Guice 文档对此有很好的解释:

和工厂一样,依赖注入只是一种设计模式.核心原则是将行为与依赖解析分离.

Dependency Injection

Like the factory, dependency injection is just a design pattern. The core principle is to separate behaviour from dependency resolution.

依赖注入模式导致代码模块化且可测试,Guice 使其易于编写.要使用 Guice,我们首先需要告诉它如何将我们的接口映射到它们的实现.此配置在 Guice 模块中完成,Guice 模块是任何实现 Module 接口的 Java 类.

The dependency injection pattern leads to code that's modular and testable, and Guice makes it easy to write. To use Guice, we first need to tell it how to map our interfaces to their implementations. This configuration is done in a Guice module, which is any Java class that implements the Module interface.

然后,Playframework 为 WSClient配置.这两个模块都为 Guice 提供了关于如何构建这些依赖项的足够信息,并且有一些模块描述了如何构建 WSClientConfiguration 所需的依赖项.同样,Guice 文档对此有很好的解释:

And then, Playframework declare modules for WSClient and for Configuration. Both modules gives Guice enough information about how to build these dependencies, and there are modules to describe how to build the dependencies necessary for WSClient and Configuration. Again, Guice docs has a good explanation about it:

通过依赖注入,对象在其构造函数中接受依赖.要构造一个对象,首先要构建它的依赖项.但是要构建每个依赖项,您需要它的依赖项,依此类推.所以当你构建一个对象时,你真的需要构建一个对象图.

With dependency injection, objects accept dependencies in their constructors. To construct an object, you first build its dependencies. But to build each dependency, you need its dependencies, and so on. So when you build an object, you really need to build an object graph.

在我们的例子中,对于 HelloWorldService,我们使用了构造函数注入a> 使 Guice 能够设置/创建我们的对象图.

In our case, for HelloWorldService, we are using constructor injection to enable Guice to set/create our object graph.

就像WSClient 有一个模块来描述一个实现如何绑定到一个接口/特征,我们可以对HelloWorldService 做同样的事情.Play docs 对如何创建和配置模块有明确的解释,所以我不会不要在这里重复.

Just like WSClient has a module to describe how an implementation is binded to an interface/trait, we can do the same for HelloWorldService. Play docs has a clear explanation about how to create and configure modules, so I won't repeat it here.

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

But after creating an module, to inject a HelloWorldService to your controller, you just declare it as a dependency:

class MyController @Inject() (service: Microservice) extends Controller {

    def index = Action {
        // access "service" here and do whatever you want 
    }
}

这篇关于Play Framework 2.5 中抽象类和对象的依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆