Ответ 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
, и здесь у нас есть два варианта:
- Используйте контекст выполнения по умолчанию, который будет
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 имеет хорошее объяснение:
Внедрение зависимостиПодобно 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
}
}