Перейти к основному содержанию

Продвинутые темы

В предыдущей части мы изучили общие принципы понятий "Инъекции зависимости" и ActiveJ Inject features. В этой части мы перейдем к более продвинутым и сложным случаям использования ActiveJ Inject.

Полные исходные тексты примеров вы можете найти на GitHub

DI Multibinder#

Multibinder разрешает конфликты привязок, если для одного ключа существует две или более привязок. В следующем примере мы создадим HTTP-сервер, состоящий из двух AbstractModule. Оба модуля включают 2 конфликтующих ключа. В примере мы будем использовать различные способы обеспечения мультипривязки.

В первом сервлете AbstractModuleмы обеспечиваем multibind для карты String и AsyncServlet путем переопределения метода configure() . Мы используем метод multibindToMap , который возвращает привязку карты для предоставленных конфликтующих карт привязки:

static class ServletMapsModule extends AbstractModule {  @Override  protected void configure() {    multibindToMap(String.class, AsyncServlet.class);  }
  @Provides  public Map<String, AsyncServlet> firstPage() {    return singletonMap("/first",        request -> HttpResponse.ok200().withPlainText("Hello from first page!"));  }
  @Provides  public Map<String, AsyncServlet> lastPage() {    return singletonMap("/last",        request -> HttpResponse.ok200().withPlainText("Hello from last page!"));  }
  @ProvidesIntoSet  AsyncServlet primary(Map<String, AsyncServlet> initializers) {    RoutingServlet routingServlet = RoutingServlet.create();    initializers.forEach(routingServlet::map);    return routingServlet;  }}

Обратите внимание, что основной сервлет помечен аннотацией @ProvidesIntoSet . Мы будем использовать это позже. Во втором модуле сервлета мы автоматически настроим мультипривязанность с помощью встроенной аннотации @ProvidesIntoSet . Эта аннотация предоставляет результаты в виде набора singleton, который затем предоставляется нашему основному AsyncServlet:

static class ServletInitializersModule extends AbstractModule {  @ProvidesIntoSet  public Consumer<RoutingServlet> firstPage() {    return routingServlet ->        routingServlet.map("/first",            request -> HttpResponse.ok200().withPlainText("Hello from first page!"));  }
  @ProvidesIntoSet  public Consumer<RoutingServlet> lastPage() {    return routingServlet ->        routingServlet.map("/last",            request -> HttpResponse.ok200().withPlainText("Hello from last page!"));  }
  @ProvidesIntoSet  AsyncServlet primary(Set<Consumer<RoutingServlet>> initializers) {    RoutingServlet routingServlet = RoutingServlet.create();    initializers.forEach(initializer -> initializer.accept(routingServlet));    return routingServlet;  }}

Наконец, мы можем собрать все модули вместе. Помните, мы отметили первичные ** сервлеты с помощью аннотации @ProvidesIntoSet ? Теперь мы можем просто объединить, а затем скомпилировать их с помощью Injector.of().

public static void main(String[] args) {  Injector injector = Injector.of(new ServletMapsModule(), new ServletInitializersModule());
  String s = injector.getInstance(new Key<Set<AsyncServlet>>() {}).toString();  System.out.println(s);}

Примеры источников можно найти на GitHub

Инжектор экземпляра#

InstanceInjector может инжектировать экземпляры в @Inject поля и методы некоторых уже существующих объектов. Рассмотрим этот простой пример:

@InjectString message;
@ProvidesString message() {  return "Hello, world!";}
@Overrideprotected void run() {  System.out.println(message);}
public static void main(String[] args) throws Exception {  Launcher launcher = new InstanceInjectorExample();  launcher.launch(args);}

Вопрос, который может вас беспокоить - как Launcher на самом деле узнает, что переменная message содержит "Hello, world!" строку, чтобы отобразить ее в методе run() ? Здесь, во время внутренней работы DI, InstanceInjector фактически оказывает помощь launcher:

private void postInjectInstances(String[] args) {  Injector injector = this.createInjector(args);  InstanceInjector<Launcher> instanceInjector = injector.getInstanceInjector(Launcher.class);  instanceInjector.injectInto(this);}
  • createInjector производит инжектор с заданными аргументами.
  • экземплярИнжектор получает все необходимые данные от инжектора **.
  • injectInto вводит данные в наши пустые экземпляры. Примеры источников можно найти на GitHub

Генераторы переплетов#

Рассмотрим пример Cookies form предыдущей части. На этот раз у нас те же POJO ингредиенты, но теперь наше печенье является общим Cookie<T> и имеет поле Optional<T> pastry:

static class Cookie<T> {  private final Optional<T> pastry;
  @Inject  Cookie(Optional<T> pastry) {    this.pastry = pastry;  }
  public Optional<T> getPastry() {    return pastry;  }}

Далее мы создаем AbstractModule cookbook и переопределяем его метод configure() :

AbstractModule cookbook = new AbstractModule() {  @Override  protected void configure() {    // note (1)    generate(Optional.class, (bindings, scope, key) -> {      Binding<Object> binding = bindings.get(key.getTypeParameter(0));      return binding != null ?          binding.mapInstance(Optional::of) :          Binding.toInstance(Optional.empty());    });
    bind(new Key<Cookie<Pastry>>() {});  }

generate() добавляет BindingGenerator для данного класса в данный модуль, в данном случае это Optional. BindingGenerator пытается сгенерировать недостающее связывание зависимостей, когда Injector компилирует окончательный граф связывания trie. Вы можете заменить generate() следующим кодом:

@Provides<T> Optional<T> pastry(@io.activej.di.annotation.Optional T instance) {    return Optional.ofNullable(instance);

Теперь вы можете создать cookbook injector и получить экземпляр Cookie<Pastry>:

Injector injector = Injector.of(cookbook);System.out.println(injector.getInstance(new Key<Cookie<Pastry>>() {}).getPastry().orElseThrow(AssertionError::new).getButter().getName());

Примеры источников можно найти на GitHub

Переходные привязки#

Если вам нужен объект, не являющийся синглтоном, чтобы каждая привязка получала свой собственный экземпляр, используйте Transient Bindings. Просто добавьте аннотацию @Transient :

AbstractModule cookbook = new AbstractModule() {  @Provides  @Transient  Integer giveMe() {    return random.nextInt(1000);  }};

После создания инжектора книги рецептов , каждый раз, когда вы будете использовать injector.getInstance, вы будете получать новый не синглтон Integer** экземпляр:

Injector injector = Injector.of(cookbook);Integer someInt = injector.getInstance(Integer.class);Integer otherInt = injector.getInstance(Integer.class);System.out.println("First : " + someInt + ", second  : " + otherInt);

Вывод покажет, что созданные экземпляры отличаются друг от друга, и будет выглядеть примерно так:

Первый: 699, второй: 130

Примеры источников можно найти на GitHub

Оптимизация специалиста ActiveJ#

ActiveJ Specializer - это библиотека, которая оптимизирует код для JVM. Вы можете просто объединить его с ActiveJ Inject и ускорить свой код на up to 30% Чтобы настроить ActiveJ Specializer, просто используйте Injector.useSpecializer() перед Injector инстанцированием. ActiveJ Inject компилирует привязки во время выполнения в высокоэффективное представление. В сочетании с ActiveJ Specializer привязки могут быть превращены в байткод, который будет столь же эффективен, как и написанный вручную код. Таким образом, вы получаете наилучшую производительность в реальных проектах без необходимости поддерживать сложный ручной код.

Поставщик экземпляров#

InstanceProvider это версия `Injector.getInstance()` с встроенным ключом. Его можно свободно запрашивать методами провайдера.

В AbstractModule мы явно добавляем InstanceProvider привязку для Integer с помощью bindInstanceProvider вспомогательного метода и предоставляем Integer фабричную функцию:

AbstractModule cookbook = new AbstractModule() {  @Override  protected void configure() {    bindInstanceProvider(Integer.class);  }
  @Provides  Integer giveMe() {    return random.nextInt(1000);  }};

После создания инжектора ** книги рецептов , мы получаем экземпляр ключа <InstanceProvider<Integer>>. Теперь просто используйте provider.get() , чтобы получить ленивый экземпляр Integer .

Injector injector = Injector.of(cookbook);InstanceProvider<Integer> provider = injector.getInstance(new Key<InstanceProvider<Integer>>() {});// lazy value get.Integer someInt = provider.get();System.out.println(someInt);

В отличие от предыдущего примера, если вы вызовете provide.get() несколько раз, вы получите одно и то же значение. Примеры источников можно найти на GitHub

Инспектирование созданного графа зависимостей#

ActiveJ Inject предоставляет эффективный DSL для осмотра созданных экземпляров, диапазонов и визуализации графа зависимостей. В этом примере печенья мы, как обычно, создаем Сахар, Масло, Мука, Кондитерские изделия и Печенье POJOs, cookbook AbstractModule с двумя scopes (parent scope для Cookie и @OrderScope для ингредиентов) и cookbook injector. Сначала рассмотрим три метода Injector : peekInstance, hasInstance и getInstance. Они позволяют проверять созданные экземпляры:

Cookie cookie1 = injector.peekInstance(Cookie.class);System.out.println("Instance is present in injector before 'get' : " + injector.hasInstance(Cookie.class));System.out.println("Instance before get : " + cookie1);
Cookie cookie = injector.getInstance(Cookie.class);
Cookie cookie2 = injector.peekInstance(Cookie.class);System.out.println("Instance is present in injector after 'get' : " + injector.hasInstance(Cookie.class));System.out.println("Instance after get : " + cookie2);System.out.println();    /// created instance check.System.out.println("Instances are same : " + cookie.equals(cookie2));
  • peekInstance - возвращает экземпляр только если он уже был создан вызовом getInstance до этого.
  • hasInstance - проверяет, был ли экземпляр предоставленного ключа создан вызовом getInstance до того, как он был создан.
  • getInstance - возвращает экземпляр предоставленного ключа . Далее мы изучим инструменты для инспектирования диапазонов:
final Scope ORDER_SCOPE = Scope.of(OrderScope.class);
System.out.println("Parent injector, before entering scope : " + injector);
Injector subInjector = injector.enterScope(ORDER_SCOPE);System.out.println("Parent injector, after entering scope : " + subInjector.getParent());System.out.println("Parent injector is 'injector' : " + injector.equals(subInjector.getParent()));
System.out.println("Pastry binding check : " + subInjector.getBinding(Pastry.class));
  • getParent - возвращает родительскую область видимости текущей области видимости
  • getBinding - возвращает зависимости предоставленной привязки
  • getBindings - возвращает зависимости предоставленной области видимости (включая Injector).
Utils.printGraphVizGraph(subInjector.getBindingsTrie());

Вы получите следующий результат:

digraph {    rankdir=BT;    "()->DiDependencyGraphExplore$Flour" [label="DiDependencyGraphExplore$Flour"];    "()->DiDependencyGraphExplore$Sugar" [label="DiDependencyGraphExplore$Sugar"];    "()->DiDependencyGraphExplore$Butter" [label="DiDependencyGraphExplore$Butter"];    "()->DiDependencyGraphExplore$Cookie" [label="DiDependencyGraphExplore$Cookie"];    "()->io.activej.di.core.Injector" [label="Injector"];    "()->DiDependencyGraphExplore$Pastry" [label="DiDependencyGraphExplore$Pastry"];
    { rank=same; "()->DiDependencyGraphExplore$Flour" "()->DiDependencyGraphExplore$Sugar" "()->DiDependencyGraphExplore$Butter" "()->io.activej.di.core.Injector" }
    "()->DiDependencyGraphExplore$Cookie" -> "()->DiDependencyGraphExplore$Pastry";    "()->DiDependencyGraphExplore$Pastry" -> "()->DiDependencyGraphExplore$Butter";    "()->DiDependencyGraphExplore$Pastry" -> "()->DiDependencyGraphExplore$Flour";    "()->DiDependencyGraphExplore$Pastry" -> "()->DiDependencyGraphExplore$Sugar";}

Который можно преобразовать в следующий график:

graph BT DiDependencyGraphExplore$Cookie --> id1(DiDependencyGraphExplore$Pastry) id1 --> DiDependencyGraphExplore$Flour id1 --> DiDependencyGraphExplore$Sugar id1 --> DiDependencyGraphExplore$Butter Инжектор

Примеры источников можно найти на GitHub

Дополнительный модуль генератора#

OptionalGeneratorModule работает аналогично предыдущему модулю генератора с той разницей, что OptionalGeneratorModule отвечает за создание объектов Optional .

  • В следующем примере нам понадобится экземпляр Дополнительно<String>.
  • Рецепт создания помещен в модуль **.
  • install() устанавливает OptionalGeneratorModule для дальнейшего автоматического создания дополнительного объекта.
  • Тогда мы просто связываем рецепт String и в следующей строке указываем привязку для создания экземпляра для ключа Optional<String>.
  • В конце концов, мы просто создаем инъектор модуля, попросите его получить экземпляр Опционально<String> и получите "Здравствуйте, Мир".
public class OptionalGeneratorModuleExample {  public static void main(String[] args) {    Injector injector = Injector.of(ModuleBuilder.create()        .install(OptionalGeneratorModule.create())        .bind(String.class).toInstance("Hello, World")        .bind(new Key<Optional<String>>() {})        .build());    Optional<String> instance = injector.getInstance(new Key<Optional<String>>() {});    System.out.println(instance);  }}

Примеры источников можно найти на GitHub