Подтвердить что ты не робот

Вложение зависимостей с абстрактным классом и объектом в Play Framework 2.5

Я пытаюсь перейти от 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

Но теперь вы должны использовать инъекцию зависимостей для использования WS, а также для использования доступа к текущему приложению воспроизведения.

У меня есть что-то вроде этого (сокращенно):

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, configuration, 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, надлежащим образом (не используя устаревшие методы), не делая вещи такими сложными?

4b9b3361

Ответ 1

Это больше связано с тем, как 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, и здесь у нас есть два варианта:

Это зависит от вас, но вы можете легко ввести 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 имеет хорошее объяснение:

Внедрение зависимости

Подобно factory, инъекция зависимостей - это всего лишь шаблон проектирования. Основной принцип заключается в том, чтобы отделить поведение от разрешения зависимостей.

Схема инъекции зависимостей приводит к тому, что модуль является модульным и проверяемым, а Guice упрощает запись. Чтобы использовать Guice, сначала нужно сказать, как сопоставить наши интерфейсы с их реализациями. Эта конфигурация выполняется в модуле Guice, который представляет собой любой класс Java, который реализует интерфейс модуля.

И затем, Playframework объявит модули для WSClient и для Конфигурация. Оба модуля дают Guice достаточную информацию о том, как создавать эти зависимости, а также есть модули для описания того, как строить зависимости, необходимые для WSClient и Configuration. Опять же, Guice docs имеет хорошее объяснение:

При инъекции зависимостей объекты принимают зависимости в своих конструкторах. Чтобы построить объект, вы сначала создаете его зависимости. Но для построения каждой зависимости вам нужны ее зависимости и т.д. Поэтому, когда вы создаете объект, вам действительно нужно построить граф объектов.

В нашем случае для HelloWorldService мы используем впрыск конструктора, чтобы позволить Guice устанавливать/создавать наш графический объект.

Как вводится HelloWorldService?

Так же, как WSClient имеет модуль для описания того, как реализация привязана к интерфейсу/признаку, мы можем сделать то же самое для HelloWorldService. Воспроизвести документы имеет четкое объяснение о том, как создавать и настраивать модули, поэтому я не буду повторять его здесь.

Но после создания модуля, чтобы ввести HelloWorldService в ваш контроллер, вы просто объявляете его как зависимость:

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

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

Ответ 2

В scala,

- > Если вы не хотите явно перенаправлять все введенные параметры базовому конструктору, вы можете сделать это следующим образом:

abstract class Base {
  val depOne: DependencyOne
  val depTwo: DependencyTwo
  // ...
}

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