跳到主要内容

实例

为了表现ActiveJ Inject的主要概念和功能,我们创建了一个例子,从低级的DI概念开始 ,并逐渐涵盖更具体的高级功能。

note

要运行例子,你需要从GitHub克隆ActiveJ

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

并将其作为一个Maven项目导入。 查看标签 v5.0-beta2。 在运行这些例子之前,先建立项目。 这些例子位于 activej/core-inject/test ,命名为 DiFollowUpTest

This example on GitHub

在这个例子中,我们有一个厨房,在那里你可以用奇妙的ActiveJ Inject自动创建美味的饼干。 在我们开始做饭之前,请注意,有几个POJO的默认构造函数标有 @Inject 注解: Kitchen, Sugar, Butter, Flour, PastryCookie

手动装订#

让我们用ActiveJ Inject的方式硬生生地烤出一个 Cookie。 首先,我们需要提供饼干的所有材料: 黄油面粉。 接下来,有一个 糕点的配方,其中包括 材料(黄油面粉)我们已经知道如何获得。 最后,我们可以添加一个配方, ,如何烤出一个 饼干

graph BT id1(Cookie) --> id2(Pastry) id2 --> 黄油 id2 --> 面粉 id2 --> 糖

现在是烘烤时间! 只要用所有这些配方创建 Injector ,并要求它提供你的 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烤制一个 饼干。 我们将 捆绑 我们的配方 , 黄油面粉 在“cookbook” 模块 不要直接创建绑定并将其存储在地图上。 我们只在 绑定在我们的模块中的 配方和 然后将其交给 注入器

public void moduleBindSnippet() {  Module module = ModuleBuilder.create()      .bind(Sugar.class).to(() -> new Sugar("WhiteSugar", 10.0f))      .bind(Butter.class).to(() -> new Butter("PerfectButter", 20.0f))      .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)      .build();
  Injector injector = Injector.of(module);  assertEquals("PerfectButter", injector.getInstance(Cookie.class).getPastry().getButter().getName());}

使用 @Provides进行绑定#

是时候进行真正的 饼干 业务了。 我们将使用声明式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());}

如果你的班级提供了一个方案,你可以轻松地使用它。

public void scanClassSnippet() {  Module cookbook = ModuleBuilder.create().scan(InjectsDefinition.class).build();
  Injector injector = Injector.of(cookbook);  assertEquals("PerfectButter", injector.getInstance(Cookie.class).getPastry().getButter().getName());}

使用 @Inject进行自动绑定#

当我们创建我们的POJO时,我们已经用 @Inject 注释标记了它们的构造函数。

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

如果一个绑定依赖于一个没有已知绑定的类, injector 将尝试为它自动生成绑定。 它将在其构造函数、静态工厂方法或类本身上搜索 @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 注解,并提供两个不同的 SugarPastryCookie 工厂函数。 这种方法允许使用同一类别的不同实例。 现在我们可以告诉我们的 注射器,我们需要哪种饼干--普通的或无糖的。

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 范围。

@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个饼干的实例。

  • 首先,我们注入一个 Kitchen的实例。 现在这个实例被存储在根作用域注入器中。
  • 接下来,我们创建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());}

现在你将收到一个输出,它将代表一个实例被创建的时间和实例本身。