5. Dagger2之Subcomponent和Scope

译文

Subcomponent(子组件)是继承和扩展父Component(组件)的对象图的component.你可以使用它们将应用的对象图划分为子图,或者封装成不同的部分,而不是在一个component中共用一个scope(作用域)。

绑定在一个Subcomponent中的对象可以基于任意绑定到它的parent component或ancestor(祖先,上级) component的对象,以及在自己的module中绑定的对象。另一方面,parent Component找中绑定的对象不能基于在Subcomponent中绑定的对象。在一个Subcomponent中绑定的对象也不能基于兄弟Subcomponent中绑定的对象。

也就是说,Subcomponent的父Component的对象图是Subcomponent本身的对戏那个图的子图。

声明一个Subcomponent

正如最高级的Component,你可以写一个抽象类或接口,在其中声明一些抽象方法,这些方法返回你的应用要用到的一些类型,由此创建一个Subcomponent. 在这里我们不使用@Component标注一个Subcomponent,而是使用@Subcomponent并安装(install) @Modules. 与Component builder类似,一个@Subcomponent Builder指定了一个提供必要modules的接口用来构造Subcomponent.

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

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

添加一个Subcomponent到parent Component

要添加一个Subcomponent到一个parent Component中,需要将这个Subcomponent类添加到parent Component安装的@Modulesubcomponent属性中。然后就可以在parent内部请求Subcomponentbuilder.

@Module(subcomponents = RequestComponent.class)
class ServerModule {}

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

@Singleton
class RequestRouter {
  @Inject RequestRouter(
      Provider<RequestComponent.Builder> requestComponentProvider) {}

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

Subcomponents之Scope(作用域)

将应用的Component拆分到Subcomponent的理由之一就是使用Scopes.一般情况下,unscoped的绑定,注入类型的每个使用者会获得一个新的,单独的实例。但是,如果绑定是scoped,那么在该scope的生命期间,那个binding的所有使用者拿到的是该绑定类型的同一实例。

标准的scope@Singleton(单例). 单例的bindings的所有用户拿到的都是相同实例。

在Dagger中,通过使用@Scope注解一个Component可以与一个Scope关联。那样,Component的实现例持有所有scoped的对象的引用,因此他们可以被复用。带有scope注解的@Provides方法的Modules只会安装在标注有同样scopeComponent中。

带有@Inject构造方法的类型也可以使用Scope注解标注。这些隐式bindings可以被任意使用了该注解的Component或者他的SubComponent使用Scoped的实例会在正确的scope中被绑定。

任何Subcomponent不能关联和祖先Comonent同样的scope,虽然没有关联的两个Subcomponent可以使用同样的scope,这是因为没有关于scoped对象的存储位置的冲突(即使两个Subcomponent使用相同的scope注解,他们仍然能有效持有不同scope的实例)。

例如,在下面的component树,BadChildComponent与它的父亲RootComponent有同样的@RootScope注解,这就错啦。但是SiblingComponentOneSiblingComponentTwo都可以使用@ChildScope,因为一个(SiblingComponent)中的binding不会与另一个(SiblingComponent)相同类型的binding混淆。

@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 {...}

因为一个Subcomponent是在其父亲内部创建的,其生命周期严格小于它的父亲。这意味着,将Subcomponentscopes视为”更小”,将父级Componentscopes视为”更大”是有意义的。事实上,你几乎总是要让根Component使用@Singleton注解。

下面的实例中,RootComponent@Singleton注解中。@SessionScope嵌套在@Singleton scope中,@RequestScope嵌套在@SessionScope中。注意FootRequestComponentBarRequestComponent都用的是@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 {...}

Subcomponent之封装

使用Subcomponents的另一个原因是封装应用中彼此不同的部分。例如,服务器中的两个service(或者你应用的两个页面)共享一些bindings,用于验证和授权,但双方其他的一些bindings彼此毫不相关,为两个service或页面创建隔离的两个Subcomponents,然后将共有的bindings放进parent Component中。

在下面的例子中,Database是在@singleton Component中提供,但它所有的实现细节封装在DatabaseComponent中。另外确保没有UI会不通过Database访问DatabaseConnectionPool去调度(Schedule)他们自己的请求(queries),因为那个binding只存在于Subcomponent中。

@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();
}

使用抽象工厂方法定义Subcomponent

除了@Module.subcomponents, 通过在parent Component中声明一个返回Subcomponent的抽象工厂方法也可以将subcomponent安装到parent中。如果Subcomponent需要一个没有public无参构造方法的module,并且该module没有安装到parent Component中,那么该工厂方法必须有一个该Module类型的参数。工厂方法可以有其他任何安装在Subcomponent上但没有安装在parent component上的module参数(Subcomponent将自动共享它与其parent之间共享的module实例)。可选地,parent component上的抽象方法可以返回@Subcomponent.Builder,并且不需要将module列为参数。

使用@Module.subcomponent更好,因为它允许Dagger检测该Subcomponent曾经是否请求过。通过parent component上的方法安装Subcomponent是对那个Component的显示请求,计时那个方法从未调用过。Dagger无法检测到这个,于是必须生成该Subcomponent,计时你从未使用到它。

细节

扩展multibindings(多绑定,复式绑定)

如他其他bindings一样,parent component中的multibindings 对于Subcomponent中的bindings是可见的。但是Subcomponent也可以添加multibindingsmaps中,并设置绑定到它们的parent. 这样的绑定(Any such additional contributions )只对subcomponent内部或其subcomponentbindings可见,在parent内是不可见的。

@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");

重复的modules

当同样的module类型安装在一个component和任意一个它的subcomponent中,每个这样的component将自动使用该module的同一实例。这意味着如果你调用一个重复modulesbcomponent builder方法或者调用一个以重复module为参数的subcomponent工厂方法,就会出错(前者在编译时无法检测,是一个运行时错误)。

@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();

解析

暂无

猜你喜欢

转载自blog.csdn.net/kevinscsdn/article/details/79105061