[译]Dagger子组件

549 阅读6分钟

子组件是继承和扩展父组件的对象图的组件。您可以使用它们将应用程序的对象图划分为子图,以将应用程序的不同部分彼此封装或在组件中使用多个范围。

一个子组件中的一个对象绑定可以依赖被绑定在它父组件或任何祖先组件中的任何对象,以及被绑定在自己模块中的对象。另一方面,在父组件中绑定的对象不能依赖于在子组件中绑定的对象;在一个子组件中绑定的对象也不能依赖于在同级子组件中绑定的对象。

换句话说,子组件的父组件的对象图是子组件本身的对象图的子图。

声明一个子组件

就像顶级组件一样,通过编写一个抽象类或接口来创建子组件,该抽象类或接口声明抽象方法,以返回您的应用程序关心的类型。 您可以使用@Subcomponent对其进行注释并安装@Modules,而不是使用@Component注释子组件。 类似于组件构建器,@ Subcomponent.Builder指定了一个接口来提供必要的模块来构造子组件。

@Subcomponent(modules = RequestModule.class)
interface RequestComponent {
  RequestHandler requestHandler();

  @Subcomponent.Builder
  interface Builder {
    Builder requestModule(RequestModule module);
    RequestComponent build();
  }
}

为父组件添加子组件

要将子组件添加到父组件,请将子组件类添加到父组件的@Modulesubcomponents属性中。然后可以从父级请求子组件的构建器。

//添加子组件
@Module(subcomponents = RequestComponent.class)
class ServerModule {}

@Singleton
@Component(modules = ServerModule.class)
interface ServerComponent {
  RequestRouter requestRouter();
}

@Singleton
class RequestRouter {
  private final Provider<RequestComponent.Builder> requestComponentProvider;

  @Inject RequestRouter(
      Provider<RequestComponent.Builder> requestComponentProvider) {
    this.requestComponentProvider = requestComponentProvider;
  }

  void dataReceived(Data data) {
    RequestComponent requestComponent =
        requestComponentProvider.get()
            .requestModule(new RequestModule(data))
            .build();
    requestComponent.requestHandler()
        .writeResponse(200, "hello, world");
  }
}

子组件和范围

将应用程序组件分解为子组件的一个原因是使用范围。使用普通的无限制绑定,注入类型的每个用户可能会获得一个新的独立实例。 但是如果绑定是有作用域的,那么在该作用域生命周期内该绑定的所有用户将获得绑定类型的相同实例。

标准范围是@Singleton。单例作用域绑定的用户都获得相同的实例。

Dagger中,组件可以通过使用@Scope注解进行注释来与范围相关联。在这种情况下,组件实现保存对所有范围对象的引用,以便它们可以重用。带有@Provides方法的模块只能用一个作用域注释的组件注释。

(带有@Inject构造函数的类型也可以使用作用域注释进行注释,这些“隐式绑定”可以被任何用该作用域或其任何后代组件注解的组件使用,范围实例将被绑定在正确的作用域中。

没有子组件可以与任何祖先组件相同的范围相关联,尽管不可相互可达的两个子组件可以与相同范围相关联,因为关于在哪里存储范围对象没有歧义。 (即使使用相同的作用域注释,这两个子组件实际上也有不同的作用域实例。)

例如,在下面的组件树中,BadChildComponent具有与其父级RootComponent相同的@RootScope注释,并且这是一个错误。但是SiblingComponentOne和SiblingComponentTwo都可以使用@ChildScope,因为无法将一个绑定与另一个绑定绑定为相同类型的绑定。

@RootScope @Component
interface RootComponent {
  BadChildComponent.Builder badChildComponent(); // ERROR!
  SiblingComponentOne.Builder siblingComponentOne();
  SiblingComponentTwo.Builder siblingComponentTwo();
}

@RootScope @Subcomponent
interface BadChildComponent {...}

@ChildScope @Subcomponent
interface SiblingComponentOne {...}

@ChildScope @Subcomponent
interface SiblingComponentTwo {...}

由于子组件是从其父项创建的,因此其生命周期严格小于其父项。这意味着将子组件的范围视为“更小”和父组件的范围“更大”是有意义的。 事实上,你几乎总是希望根组件使用@Singleton范围。

在下面的例子中,RootComponent在@Singleton范围内。 @SessionScope嵌套在@Singleton范围内,@RequestScope嵌套在@SessionScope中。 请注意,FooRequestComponent和BarRequestComponent都与@RequestScope相关联,因为它们是兄弟姐妹; 既不是另一个的祖先。

@Singleton @Component
interface RootComponent {
  SessionComponent.Builder sessionComponent();
}

@SessionScope @Subcomponent
interface SessionComponent {
  FooRequestComponent.Builder fooRequestComponent();
  BarRequestComponent.Builder barRequestComponent();
}

@RequestScope @Subcomponent
interface FooRequestComponent {...}

@RequestScope @Subcomponent
interface BarRequestComponent {...}

子组件

使用子组件的另一个原因是将应用程序的不同部分彼此封装在一起。例如,如果服务器中的两个服务(或应用程序中的两个屏幕)共享一些绑定,例如用于身份验证和授权的绑定,但每个服务都有其他绑定,而这些绑定实际上没有任何关系,那么创建 为每个服务或屏幕分开子组件,并将共享绑定放入父组件。

在以下示例中,数据库在@Singleton组件中提供,但其所有实现细节都封装在DatabaseComponent中。请放心,因为该绑定仅存在于子组件中,所以没有UI将访问DatabaseConnectionPool来安排自己的查询而不通过数据库。

@Singleton
@Component(modules = DatabaseModule.class)
interface ApplicationComponent {
  Database database();
}

@Module(subcomponents = DatabaseComponent.class)
class DatabaseModule {
  @Provides
  @Singleton 
  Database provideDatabase(
      @NumberOfCores int numberOfCores,
      DatabaseComponent.Builder databaseComponentBuilder) {
    return databaseComponentBuilder
        .databaseImplModule(new DatabaseImplModule(numberOfCores / 2))
        .build()
        .database();
  }
}

@Module
class DatabaseImplModule {
  DatabaseImplModule(int concurrencyLevel) {}
  @Provides DatabaseConnectionPool provideDatabaseConnectionPool() {}
  @Provides DatabaseSchema provideDatabaseSchema() {}
}

@Subcomponent(modules = DatabaseImplModule.class)
interface DatabaseComponent {
  @PrivateToDatabase Database database();
}

使用工厂方法定义子组件

除了@ Module.subcomponents之外,通过在返回子组件的父组件中声明抽象工厂方法,可以将子组件安装在父组件中。如果子组件需要一个没有无参数公共构造函数的模块,并且该模块未安装到父组件中,那么工厂方法必须具有该模块类型的参数。工厂方法可以为安装在子组件上但不在父组件上的任何其他模块提供其他参数。 (子组件将自动共享它与其父代之间共享的任何模块的实例。)或者,父组件上的抽象方法可能会返回@ Subcomponent.Builder,并且不需要将模块列为参数。

使用@ Module.subcomponents更好,因为它允许Dagger检测子组件是否被请求过。通过父组件上的方法安装子组件是对该组件的明确请求,即使该方法从未被调用。匕首无法检测到,因此即使您从不使用它也必须生成子组件。

细节

扩展多重绑定

像其他绑定一样,父组件中的多重绑定对子组件中的绑定可见。但子组件也可以将多重绑定添加到其父级中的映射和集合。 任何此类附加贡献仅对子组件或其子组件内的绑定可见,并且在父代中不可见。

@Component(modules = ParentModule.class)
interface Parent {
  Map<String, Integer> map();
  Set<String> set();

  Child child();
}

@Module
class ParentModule {
  @Provides @IntoMap
  @StringKey("one") static int one() {
    return 1;
  }

  @Provides @IntoMap
  @StringKey("two") static int two() {
    return 2;
  }

  @Provides @IntoSet
  static String a() {
    return "a"
  }

  @Provides @IntoSet
  static String b() {
    return "b"
  }
}

@Subcomponent(modules = ChildModule.class)
interface Child {
  Map<String, Integer> map();
  Set<String> set();
}

@Module
class ChildModule {
  @Provides @IntoMap
  @StringKey("three") static int three() {
    return 3;
  }

  @Provides @IntoMap
  @StringKey("four") static int four() {
    return 4;
  }

  @Provides @IntoSet
  static String c() {
    return "c"
  }

  @Provides @IntoSet
  static String d() {
    return "d"
  }
}

Parent parent = DaggerParent.create();
Child child = parent.child();
assertThat(parent.map().keySet()).containsExactly("one", "two");
assertThat(child.map().keySet()).containsExactly("one", "two", "three", "four");
assertThat(parent.set()).containsExactly("a", "b");
assertThat(child.set()).containsExactly("a", "b", "c", "d");

重复的模块

当相同的模块类型安装在组件及其任何子组件中时,那么每个组件都将自动使用该模块的相同实例。这意味着如果您针对重复模块调用子组件构建器方法,或者子组件工厂方法将重复模块定义为参数,则这是错误的。 (前者不能在编译时检查,因此是运行时错误。)

@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
  ComponentTwo componentTwo(RepeatedModule repeatedModule); // COMPILE ERROR!
  ComponentThree.Builder componentThreeBuilder();
}

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
  @Subcomponent.Builder
  interface Builder {
    Builder repeatedModule(RepeatedModule repeatedModule);
    ComponentThree build();
  }
}

DaggerComponentOne.create().componentThreeBuilder()
    .repeatedModule(new RepeatedModule()) // UnsupportedOperationException!
    .build();