Внедрение зависимостей с абстрактным классом и объектом в 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 а также использовать доступ к текущему приложению Play.

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

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

2 ответа

Решение

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

  1. Используйте контекст выполнения по умолчанию, который будет play.api.libs.concurrent.Execution.Implicits.defaultContext
  2. Используйте другие пулы потоков

Это зависит от вас, но вы можете легко ввести 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, который является любым классом Java, который реализует интерфейс модуля.

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

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

В нашем случае для HelloWorldService, мы используем инжектор конструктора, чтобы позволить 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 
    }
}

В скале,

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

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

case class Child @Inject() (param1: Int,
                            depOne: DependencyOne,
                            depTwo: DependencyTwo) extends Base {
  // ...
}
Другие вопросы по тегам