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

Примеры

Чтобы представить основные концепции и возможности ActiveJ Inject, мы создали пример, который начинается с низкоуровневых концепций DI и постепенно охватывает более конкретные расширенные возможности.

note

Чтобы запустить примеры, необходимо клонировать ActiveJ с GitHub

git clone https://github.com/activej/activej

И импортируйте его как проект Maven. Посмотрите тег v5.0-beta2. Перед запуском примеров выполните сборку проекта. Эти примеры расположены по адресу activej/core-inject/test и названы DiFollowUpTest.

This example on GitHub

В этом примере у нас есть кухня, где можно автоматически создавать вкусное печенье с помощью замечательного ActiveJ Inject. Прежде чем мы приступим к приготовлению, обратите внимание, что существует несколько POJO, конструкторы которых по умолчанию помечены аннотацией @Inject : Кухня, Сахар, Масло, Мука, Кондитерские изделия и Печенье.

Manual Bind#

Давайте испечем Cookie , используя ActiveJ Inject в жестком режиме. Прежде всего, нам необходимо подготовить все ингредиенты для печенья: Сахар, Масло и Мука. Далее следует рецепт кондитерских изделий, в который входят ингредиенты (сахар, масло и мука), которые мы уже знаем, как получить. Наконец, мы можем добавить рецепт как испечь печенье.

graph BT id1(Cookie) --> id2(Кондитерские изделия) id2 --> Масло id2 --> Мука id2 --> Сахар

Теперь время выпечки! Просто создайте инжектор ** со всеми этими рецептами и попросите его создать ваш экземпляр Cookie** .

public void transformBindingSnippet() {  Module cookbook = ModuleBuilder.create()      .bind(Sugar.class).to(Sugar::new)      .bind(Butter.class).to(Butter::new)      .bind(Flour.class).to(() -> new Flour("GoodFlour", 100.0f))      .bind(Pastry.class).to(Pastry::new, Sugar.class, Butter.class, Flour.class)      .bind(Cookie.class).to(Cookie::new, Pastry.class)      .transform(Object.class, (bindings, scope, key, binding) ->          binding.onInstance(x -> System.out.println(Instant.now() + " -> " + key)))      .build();
  Injector injector = Injector.of(cookbook);  assertEquals("GoodFlour", injector.getInstance(Cookie.class).getPastry().getFlour().getName());}

Связывание с помощью ModuleBuilder#

На этот раз мы испечем печенье * с простым DSL. Мы объединим* наши рецепты для сахара, масла и муки в "кулинарную книгу" модуль. Вместо того чтобы явно создавать привязки и хранить их непосредственно в карте, мы просто привязываем рецепты в нашем модуле и затем передаем его инжектору .

public void transformBindingSnippet() {  Module cookbook = ModuleBuilder.create()      .bind(Sugar.class).to(Sugar::new)      .bind(Butter.class).to(Butter::new)      .bind(Flour.class).to(() -> new Flour("GoodFlour", 100.0f))      .bind(Pastry.class).to(Pastry::new, Sugar.class, Butter.class, Flour.class)      .bind(Cookie.class).to(Cookie::new, Pastry.class)      .transform(Object.class, (bindings, scope, key, binding) ->          binding.onInstance(x -> System.out.println(Instant.now() + " -> " + key)))      .build();
  Injector injector = Injector.of(cookbook);  assertEquals("GoodFlour", injector.getInstance(Cookie.class).getPastry().getFlour().getName());}

Связывание с помощью @Provides#

Пришло время для настоящего бизнеса Cookie . Вместо того чтобы явно создавать привязки, мы будем использовать декларативный DSL. Как и в предыдущем примере, мы создаем модуль кулинарной книги, но на этот раз все привязки для ингредиентов будут созданы автоматически из методов провайдера **. Эти методы помечены аннотацией @Provides :

public void provideAnnotationSnippet() {  Module cookbook = new AbstractModule() {    @Provides    Sugar sugar() { return new Sugar("WhiteSugar", 10.f); }
    @Provides    Butter butter() { return new Butter("PerfectButter", 20.0f); }
    @Provides    Flour flour() { return new Flour("GoodFlour", 100.0f); }
    @Provides    Pastry pastry(Sugar sugar, Butter butter, Flour flour) {      return new Pastry(sugar, butter, flour);    }
    @Provides    Cookie cookie(Pastry pastry) {      return new Cookie(pastry);    }  };
  Injector injector = Injector.of(cookbook);  assertEquals("PerfectButter", injector.getInstance(Cookie.class).getPastry().getButter().getName());}

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

Иногда бывает так, что вы готовите схему инъекции, но эта схема не является модулем. Но существует метод scan() , который может помочь вам установить связь между сущностями DI и вашей схемой.

public void scanObjectSnippet() {  Module cookbook = ModuleBuilder.create()      .scan(new Object() {        @Provides        Sugar sugar() { return new Sugar("WhiteSugar", 10.f); }
        @Provides        Butter butter() { return new Butter("PerfectButter", 20.0f); }
        @Provides        Flour flour() { return new Flour("GoodFlour", 100.0f); }
        @Provides        Pastry pastry(Sugar sugar, Butter butter, Flour flour) {          return new Pastry(sugar, butter, flour);        }
        @Provides        Cookie cookie(Pastry pastry) {          return new Cookie(pastry);        }      })      .build();
  Injector injector = Injector.of(cookbook);  assertEquals("PerfectButter", injector.getInstance(Cookie.class).getPastry().getButter().getName());}

Если ваш класс предоставляет схему, вы можете легко использовать ее:

``java url=/core-inject/src/test/java/io/activej/inject/DIFollowUpTest.java tag=REGION_5


Автоматическое связывание с помощью @Inject#

Когда мы создали наши POJO, мы пометили их конструкторы аннотацией @Inject :

static class Sugar {  private final String name;  private final float weight;
  @Inject  public Sugar() {    this.name = "WhiteSugar";    this.weight = 10.f;  }

Если привязка зависит от класса, который не имеет известной привязки, инжектор попытается автоматически сгенерировать для него привязку. Он будет искать аннотацию @Inject на конструкторах, статических методах фабрики или в самом классе (в данном случае используется конструктор по умолчанию) и использовать их в качестве фабрики в сгенерированной привязке. Поскольку от привязки Cookie* ничего не зависит, по умолчанию привязки вообще не создаются. Здесь мы используем обычную привязку* , чтобы сообщить инжектору, что мы хотим, чтобы эта привязка присутствовала. Таким образом, будет сгенерировано все дерево привязок, от которых он зависит:

public void injectAnnotationSnippet() {  Module cookbook = ModuleBuilder.create().bind(Cookie.class).build();
  Injector injector = Injector.of(cookbook);  assertEquals("WhiteSugar", injector.getInstance(Cookie.class).getPastry().getSugar().getName());}

Использование аннотации @Named#

Давайте будем модными и испечем печенье без сахара. Для этого, наряду с аннотацией @Provides , мы также будем использовать аннотацию @Named и предоставим две различные фабричные функции Sugar, Pastry и Cookie . Этот подход позволяет использовать различные экземпляры одного и того же класса. Теперь мы можем сообщить нашему инжектору **, какое печенье нам нужно - обычное или без сахара.

public void namedAnnotationSnippet() {  Module cookbook = new AbstractModule() {    @Provides    @Named("zerosugar")    Sugar sugar1() { return new Sugar("SugarFree", 0.f); }
    @Provides    @Named("normal")    Sugar sugar2() { return new Sugar("WhiteSugar", 10.f); }
    @Provides    Butter butter() { return new Butter("PerfectButter", 20.f); }
    @Provides    Flour flour() { return new Flour("GoodFlour", 100.f); }
    @Provides    @Named("normal")    Pastry pastry1(@Named("normal") Sugar sugar, Butter butter, Flour flour) {      return new Pastry(sugar, butter, flour);    }
    @Provides    @Named("zerosugar")    Pastry pastry2(@Named("zerosugar") Sugar sugar, Butter butter, Flour flour) {      return new Pastry(sugar, butter, flour);    }
    @Provides    @Named("normal")    Cookie cookie1(@Named("normal") Pastry pastry) {      return new Cookie(pastry);    }
    @Provides    @Named("zerosugar")    Cookie cookie2(@Named("zerosugar") Pastry pastry) { return new Cookie(pastry); }  };
  Injector injector = Injector.of(cookbook);
  float normalWeight = injector.getInstance(Key.of(Cookie.class, "normal"))      .getPastry().getSugar().getWeight();  float zerosugarWeight = injector.getInstance(Key.of(Cookie.class, "zerosugar"))      .getPastry().getSugar().getWeight();
  assertEquals(10.f, normalWeight);  assertEquals(0.f, zerosugarWeight);}

Вы также можете использовать ModuleBuilder

public void moduleBuilderWithQualifiedBindsSnippet() {  Module cookbook = ModuleBuilder.create()      .bind(Key.of(Sugar.class, "zerosugar")).to(() -> new Sugar("SugarFree", 0.f))      .bind(Key.of(Sugar.class, "normal")).to(() -> new Sugar("WhiteSugar", 10.f))      .bind(Key.of(Pastry.class, "zerosugar")).to(Pastry::new, Key.of(Sugar.class).qualified("zerosugar"), Key.of(Butter.class), Key.of(Flour.class))      .bind(Key.of(Pastry.class, "normal")).to(Pastry::new, Key.of(Sugar.class).qualified("normal"), Key.of(Butter.class), Key.of(Flour.class))      .bind(Key.of(Cookie.class, "zerosugar")).to(Cookie::new, Key.of(Pastry.class).qualified("zerosugar"))      .bind(Key.of(Cookie.class, "normal")).to(Cookie::new, Key.of(Pastry.class).qualified("normal"))      .build();
  Injector injector = Injector.of(cookbook);
  float normalWeight = injector.getInstance(Key.of(Cookie.class, "normal"))      .getPastry().getSugar().getWeight();  float zerosugarWeight = injector.getInstance(Key.of(Cookie.class, "zerosugar"))      .getPastry().getSugar().getWeight();
  assertEquals(10.f, normalWeight);  assertEquals(0.f, zerosugarWeight);}

Экземпляры, не являющиеся синглтонами, с использованием диапазонов#

Наше печенье оказалось таким вкусным, что многие хотят его попробовать. Однако существует проблема , ActiveJ Inject по умолчанию делает экземпляры синглтонами. И все же мы не можем продавать одно и то же печенье всем нашим клиентам . К счастью, есть решение: мы можем использовать пользовательский @ScopeAnnotation @OrderScope для создания ORDER_SCOPE scope:

@ScopeAnnotation(threadsafe = false)@Target({ElementType.METHOD})@Retention(RUNTIME)public @interface OrderScope {}
public static final Scope ORDER_SCOPE = Scope.of(OrderScope.class);

Таким образом, наша поваренная книга будет выглядеть следующим образом:

Module cookbook = ModuleBuilder.create()    .bind(Kitchen.class).to(Kitchen::new)    .bind(Sugar.class).to(Sugar::new).in(OrderScope.class)    .bind(Butter.class).to(Butter::new).in(OrderScope.class)    .bind(Flour.class).to(Flour::new).in(OrderScope.class)    .bind(Pastry.class).to(Pastry::new, Sugar.class, Butter.class, Flour.class).in(OrderScope.class)    .bind(Cookie.class).to(Cookie::new, Pastry.class).in(OrderScope.class)    .build();

Таким образом, только кухня останется синглтоном:

graph BT subgraph Root Scope Kitchen subgraph N subgraph Order Scope Sugar-->id1(Pastry) Flour-->id1 Butter-->id1 id1-->Cookie end end end

Мы получили 10 заказов от наших клиентов, поэтому теперь нам нужно 10 экземпляров cookies:

  • Сначала мы вводим экземпляр Кухня. Теперь этот экземпляр хранится в инжекторе корневой области видимости.
  • Далее мы создаем 10 суб-инжекторов, которые входят в ORDER_SCOPE.
  • Каждый субинъектор создает только один экземпляр Cookie и ссылается на единственный экземпляр Kitchen их родительской корневой области видимости.
Injector injector = Injector.of(cookbook);Kitchen kitchen = injector.getInstance(Kitchen.class);Set<Cookie> cookies = new HashSet<>();for (int i = 0; i < 10; ++i) {  Injector subinjector = injector.enterScope(ORDER_SCOPE);
  assertSame(subinjector.getInstance(Kitchen.class), kitchen);  if (i > 0) assertFalse(cookies.contains(subinjector.getInstance(Cookie.class)));
  cookies.add(subinjector.getInstance(Cookie.class));}assertEquals(10, cookies.size());

Преобразующие узы#

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

public void transformBindingSnippet() {  Module cookbook = ModuleBuilder.create()      .bind(Sugar.class).to(Sugar::new)      .bind(Butter.class).to(Butter::new)      .bind(Flour.class).to(() -> new Flour("GoodFlour", 100.0f))      .bind(Pastry.class).to(Pastry::new, Sugar.class, Butter.class, Flour.class)      .bind(Cookie.class).to(Cookie::new, Pastry.class)      .transform(Object.class, (bindings, scope, key, binding) ->          binding.onInstance(x -> System.out.println(Instant.now() + " -> " + key)))      .build();
  Injector injector = Injector.of(cookbook);  assertEquals("GoodFlour", injector.getInstance(Cookie.class).getPastry().getFlour().getName());}

Теперь вы получите вывод, который будет представлять время создания экземпляра и сам экземпляр.