В информатике внедрение зависимостей определяется как шаблон, посредством которого один компонент получает другие компоненты (зависимости) извне. Было написано множество сообщений о различных реализациях внедрения зависимостей в Vertex с использованием библиотеки Google Guice. Все они хороши, и я не хочу изобретать здесь велосипед и повторять то же самое снова. Однако, на мой взгляд, это хорошая идея – дать немного более системный подход к этой теме. По моему опыту, в большинстве случаев для разработчиков не имеет большого значения реализовать базовый DI с помощью Vertex, скорее, кажется трудным включить компоненты Vertx в конвейер DI. В этом посте мы рассмотрим, как выполнить внедрение зависимостей в Vertex с помощью Google Guice и как создать базовую инъекцию, содержащую компонент ConfigRetriever (способ получения конфигурации приложения Vertx). Пожалуйста, обратите внимание, что в этой статье мы используем Futures API, поэтому она ориентирована на разработчиков Vertex 4.x.
Базовый DI с помощью Google Guice
Google Guice – это устоявшееся решение для реализации метода внедрения зависимостей в Java-приложения. Как и большинство известных библиотек, она довольно проста в использовании, но это не значит, что она каким-либо образом ограничена. Это очень гибкое и мощное решение. Основными строительными блоками фреймворка являются модули и инжекторы. Модуль используется для определения способа получения зависимостей. Инжектор служит основной точкой входа в приложение и используется для фактической инициализации компонента. Давайте приведем краткий пример, в котором используется техника внедрения конструктора. Представьте, что вы разрабатываете verticle, который имеет две внешние зависимости – класс client (который выполняет HTTP-вызовы) и класс repository (который выполняет операции с базой данных). Конечно, мы не будем здесь приводить их точные реализации, потому что это выходит за рамки данного поста. Чтобы указать эти классы зависимостей внутри вертикали, нам нужно использовать аннотацию @Inject . Если вы знакомы с Spring framework, вы найдете процесс знакомым. В принципе, нам нужно создать поля для хранения ссылок на компоненты, определить конструктор и аннотировать его с помощью @Inject , чтобы Guice знал, что мы используем инъекцию конструктора.
Взгляните на следующий фрагмент кода ниже:
class ProjectVerticle extends AbstractVerticle { private ProjectClient client; private ProjectRepository repository; @Inject ProjectVerticle(ProjectClient client, ProjectRepository repository){ this.client = client; this.repository = repository; } // ...
Еще один шаг – рассказать Guice, как создаются эти классы. Для этого нам нужно создать module . Сохраняя простоту, этот компонент определяет то, что называется привязками (опять же, если вы опытный разработчик Spring, вы называете это проводкой). Модули расширяют класс AbstractModule , предоставляемый Guice, и переопределяют метод configure() , который используется для привязок. Для нашего примера мы сделаем это простым:
class ProjectVerticleModule extends AbstractModule { @Override protected void configure() { bind(ProjectClient.class).to(ProjectClientImpl.class); bind(ProjectRepository.class).to(ProjectRepositoryImpl.class); } }
Вы можете отметить, что мы привязываем типы (интерфейсы) к экземплярам. Существует несколько типов привязок, доступных в Guice:
- Привязка экземпляра сопоставляет зависимость с конкретным экземпляром; это хороший выбор для простых зависимостей, но для больших его следует избегать, так как это замедлит время запуска. Мы используем его в следующем разделе
- Связанная привязка сопоставляет зависимость с ее реализацией, и Guice создает для нас экземпляр; это то, что мы использовали здесь
- Привязка поставщика предоставляет собственную реализацию интерфейса Provider , который используется для создания зависимостей; это относится к сложным зависимостям; в этом посте мы опустим его
Как только мы создали модуль, мы можем использовать его для создания компонентов. Для этого нам нужно использовать инжектор , который определяется как класс, который строит графики объектов, составляющих наше приложение. Инжектор берет один или несколько модулей и использует их, чтобы понять, как на самом деле предоставлять запрошенные зависимости и создавать экземпляр компонента. Взгляните на следующий пример:
@Test void injectTest(Vertx vertx, VertxTestContext context){ // create a module ProjectVerticleModule module = new ProjectVerticleModule(); // create an injector Injector injector = Guice.createInjector(module); // get an instance of verticle ProjectVerticle verticle = injector.getInstance(ProjectVerticle.class); // deploy the ProjectVerticle Futuredeploy = vertx.deployVerticle(verticle); deploy.onComplete(id -> { context.verify(() -> { String client = verticle.getProjectClientName(); String repository = verticle.getProjectRepositoryName(); Assertions.assertThat(client).isEqualTo("ProjectClientImpl"); Assertions.assertThat(repository).isEqualTo("ProjectRepositoryImpl"); context.completeNow(); }); }).onFailure(err -> context.failNow(err)); }
Вы можете отметить, что мы используем инжектор для инициализации компонента Project Vertical вместо использования new ключевое слово. Другими словами, мы можем сказать, что мы “передаем исходный код” процесса инициализации в Guice. Мы могли бы проверить, что зависимости были созданы правильно, проверив их имена. Это простой пример. Теперь давайте перейдем к более практической теме – как мы можем включить фактические компоненты вершин в конвейер DI.
Добавление ConfigRetriever
В этом подразделе мы рассмотрим немного более сложный (и реальный) пример. Часто (если не всегда) вам нужно создавать компоненты, которые используют конфигурации из внешнего мира. Наивным примером является репозиторий, который прослушивает учетные данные базы данных, или http-клиент, которому требуются токены API. В мире вершин для чтения конфигурации мы используем библиотеку vertex config library . Мы не будем охватывать все аспекты его использования; Я рекомендую вам ознакомиться с моей электронной книгой Принципы Vertex , где я подробно рассказываю об этом. Для целей этого поста вам просто нужно помнить, что ключевой компонент, выполняющий эту работу, называется ConfigRetriever . Этот класс абстрагирует определенные типы конфигурации и предоставляет единую точку входа (а также позволяет прослушивать обновления конфигурации; но это выходит за рамки данной статьи). Чтобы инициализировать его, мы используем статический фабричный метод, который требует ссылки на объект Vertex :
private ConfigRetriever configRetriever; AppVerticleModule(Vertx vertx){ configRetriever = ConfigRetriever.create(vertx); } @Override protected void configure() { bind(ConfigRetriever.class) .annotatedWith(Names.named("ConfigRetriever")) .toInstance(configRetriever); }
Здесь мы определяем зависимость, используя знакомую привязку экземпляра . Обратите внимание, что мы также используем необязательную привязку именования здесь: в классе потребителя (verticle) мы предоставляем аннотацию @Named с внедрением зависимостей конструктора.
@Inject AppVerticle(@Named("ConfigRetriever") ConfigRetriever configRetriever){ this.configRetriever = configRetriever; }
Компонент config retriever используется для получения конфигурации приложения. Это делается с помощью метода getConfig . В этом посте мы используем Futures API (Vertex 4.x) вместо обратных вызовов (Vertx 3.x + Vertx 4.x):
Futurecr = configRetriever.getConfig(); Future vert = cr.map(c -> { logger.info("Configuration obtained successfully"); Injector injector = Guice.createInjector(new ProjectVerticleModule(vertx, c)); ProjectVerticle verticle = injector.getInstance(ProjectVerticle.class); return verticle; }).onFailure(err -> { logger.warning("Unable to obtain configuration"); startPromise.fail(err); });
Давайте рассмотрим этот фрагмент кода. Первым шагом является получение будущего значения конфигурации. Затем мы сопоставляем результат, чтобы создать инжектор и инициализировать Project Vertical . Мы также предоставляем конфигурацию модулю, чтобы использовать их в зданиях зависимостей (например, учетные данные, токены и т.д.). Если ретриверу конфигурации не удается получить конфигурацию, мы завершаем весь start( процесс Приложение Вертикальное .
Следующий этап – развертывание проекта по вертикали. Взгляните на следующую реализацию:
Futuredr = vert.compose(v -> vertx.deployVerticle(v)); dr.onSuccess(id -> { logger.info("ProjectVerticle deployed"); startPromise.complete(); }).onFailure(err -> { logger.warning("Unable to deploy ProjectVerticle"); logger.warning(err.getMessage()); startPromise.fail(err); });
Будущая композиция позволяет соединить два будущих (конфигурация и развертывание). Лямбда-параметр v здесь представляет собой вершину, созданную внутри метода map() на предыдущем шаге. Тема использования Futures API выходит за рамки этого поста, поэтому мы не будем здесь подробно останавливаться на этом. В принципе, у нас есть два результата:
- onSuccess вертикаль успешно развернута, и мы завершаем Приложение Вертикальное процесс запуска
- OnFailure пошло не так! Мы проваливаем Приложение Вертикальное процесс запуска
Давайте соберем все воедино. Внутри метода main нашего вымышленного приложения мы создаем модуль и инжектор для AppVerticle и развертывает его. Здесь нам не нужно развертывать другие вершины, потому что App Verticle служит точкой входа в наше приложение и выполняет эту работу:
public static void main(String[] args) { Vertx vertx = Vertx.vertx(); Injector injector = Guice.createInjector(new AppVerticleModule(vertx)); AppVerticle appVerticle = injector.getInstance(AppVerticle.class); Futuredr = vertx.deployVerticle(appVerticle); dr.onSuccess(id -> logger.info("AppVerticle started...")) .onFailure(err -> { logger.warning("Unable to start AppVerticle"); logger.warning(err.getMessage()); }) .onComplete(r -> { vertx.close(); logger.info("Vertx closed"); }); }
Исходный код
Если вы хотите получить полный исходный код, используемый в примерах в этом посте, вы можете найти его в это репозиторий github . Не стесняйтесь исследовать его!
Вывод
Проще говоря, внедрение зависимостей – это шаблон, который позволяет “передать на аутсорсинг” создание компонентов. Существует несколько проверенных решений для реализации этой архитектуры в приложениях Java, и библиотека Google Guice является одним из них. Это просто, но в то же время мощно. В этом посте мы рассмотрели, как реализовать внедрение зависимостей в приложениях Vertex 4.x. Мы привели два примера: один – простая демонстрация основных методов Guice. Во втором используется фактический компонент вершины – ConfigRetriever , который считывает конфигурацию и предоставляет ее другим компонентам (которые, в свою очередь, также используются для создания других зависимостей). Если у вас есть вопросы по этой теме, пожалуйста, не стесняйтесь связаться со мной или оставьте комментарий ниже.
Оригинал: “https://dev.to/iuriimednikov/vertx-guice-and-config-retriever-dependency-injection-in-vertx-4-x-32ig”