JobPlus知识库 IT 软件开发 文章
翻译Dagger 2使用Subcomponent

1.前言

Subcomponent也是Component,只不过继承并扩展了父Component的依赖图。通过它可以将应用的依赖图分割成几个子图,不仅将应用的不同部分封装起来,还在一个Component中使用多个作用域。

除了自己Module提供的对象,Subcomponent中的对象还可以依赖它父(或祖先)Component中提供的对象。相反,父Component中的对象不能依赖Subcomponent提供的对象;Subcomponent中的对象也不可以依赖兄弟Subcomponent提供的对象。换句话说,父Component的依赖图是Subcomponent依赖图的一部分。

2.声明Subcomponent

与Component一样创建,写一个抽象类或接口,声明返回应用所需类型的抽象方法。这里使用@Subcomponent注解Subcomponent,并且添加相关Module。与Component.Builder相似,通过@Subcomponent.Builder指定一个接口为构造Subcomponent提供必要的Module。

@Subcomponent(modules = RequestModule.class)interface RequestComponent {  RequestHandler requestHandler();  @Subcomponent.Builder  interface Builder {    Builder requestModule(RequestModule module);    RequestComponent build();  } }3.给Component添加Subcomponent

给@Module注解的subcomponents属性添加Subcomponent类,并将此Module添加到父Component中。然后,Subcomponent.Builder能在父Component中被调用。

@Module(subcomponents = RequestComponent.class)class ServerModule {}@Singleton@Component(modules = ServerModule.class)interface ServerComponent {  RequestRouter requestRouter(); }@Singletonclass RequestRouter {  @Inject RequestRouter(      Provider<RequestComponent.Builder> requestComponentProvider) {}  void dataReceived(Data data) {    RequestComponent requestComponent =        requestComponentProvider.get()            .data(data)            .build();    requestComponent.requestHandler()        .writeResponse(200, "hello, world");  } }4.Subcomponent和作用域

将Component划分为多个Subcomponent的其中一个原因是使用作用域。通常情况下,未绑定作用域的注入类型,每次使用可能会获取到新的、独立的对象。但如果绑定了作用域,则作用范围内对它的所有使用将获取到该类型的同一个实例。标准的作用域是@Singleton,调用它注解的类型都将获得同一实例。

在Dagger中,通过作用域注解将Component与作用域关联起来。这样的话,Component持有的所有作用域对象的引用可以被复用。Module中@Provides注解的方法添加了作用域注解,那么此Module只能添加到拥有相同作用域的Component。(被@Inject注解构造方法的类型也能拥有作用域注解,这些隐式绑定能被具有相同作用域的Component及其后代Component使用,自动绑定正确的作用域。)

Subcomponent不能与任一个祖先Component拥有相同作用域,但是两个不关联的Subcomponent可以拥有相同作用域,因为对于哪里存放拥有作用域的对象不存在歧义。(两个Subcomponent明显拥有不同的作用域,即使它们使用相同的作用域注解。)

举个例子,下面的Component树中,BadChildComponent与它的父Component(RootComponent)拥有相同@RootScope注解是错误的。但是SiblingComponentOne和SiblingComponentTwo能一起使用@ChildScope注解,因为此时分别绑定相同类型的作用域并不难理解。

@RootScope @Componentinterface RootComponent {  BadChildComponent.Builder badChildComponent(); // ERROR!  SiblingComponentOne.Builder siblingComponentOne();  SiblingComponentTwo.Builder siblingComponentTwo(); }@RootScope @Subcomponentinterface BadChildComponent {...}@ChildScope @Subcomponentinterface SiblingComponentOne {...}@ChildScope @Subcomponentinterface SiblingComponentTwo {...}

因为Subcomponent从它父Component内部创建,所以它的作用域范围必须小于父Component的。注意,这里指的是作用域范围而不是依赖图的范围。实际上,大部分直接在根Component上使用@Singleton注解。

下面的例子中,RootComponent的作用域为@Singleton,@SessionScope嵌套在它之中,@RequestScope嵌套在@SessionScope之中。注意,FooRequestComponent与BarRequestComponent都被注解为@RequestScope,这没问题,因为它们是兄弟关系而不是父子关系。

@Singleton @Componentinterface RootComponent {  SessionComponent.Builder sessionComponent(); }@SessionScope @Subcomponentinterface SessionComponent {  FooRequestComponent.Builder fooRequestComponent();  BarRequestComponent.Builder barRequestComponent(); }@RequestScope @Subcomponentinterface FooRequestComponent {...}@RequestScope @Subcomponentinterface BarRequestComponent {...}5.Subcomponent的封装

另一个使用Subcomponent的原因是封装应用的不同部分。比如,服务器上有两个服务(或应用中有两个界面)共享一些数据,用来认证和授权,但是彼此都有一些数据专属于自己。好的做法是,为每个服务(界面)创建独立的Subcomponent,将共享的数据放在父Component中。

下面的例子中,Database对象在@Singleton注解的Component中被提供,但它所有的实现细节被封装在DatabaseComponent中。不用担心DatabaseConnectionPool对象仅存在于Subcomponent中,所有界面只通过Database对象来安排它们自己的查询,而不是直接访问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();  } }@Moduleclass DatabaseImplModule {  DatabaseImplModule(int concurrencyLevel) {}  @Provides DatabaseConnectionPool provideDatabaseConnectionPool() {}  @Provides DatabaseSchema provideDatabaseSchema() {} }@Subcomponent(modules = DatabaseImplModule.class)interface DatabaseComponent {  @PrivateToDatabase Database database(); }6.抽象工厂方法定义Subcomponent

除了@Module.subcomponents,还可以在父Component中声明一个返回Subcomponent的抽象工厂方法,来将两者进行关联。如果Subcomponent需要的Module没有无参公开的构造方法,且Module没有加入父Component中,那么工厂方法必须含有Module类型的参数。对于加入了Subcomponent但没有加入父Component的Module,工厂方法也需要提供参数。(Subcomponent将自动共享需在它和它父Component之间共享的Module实例。)另外,父Component的抽象方法可能返回@Subcomponent.Builder,又或者没有Module需要被列为参数。

建议使用@Module.subcomponents,因为它允许Dagger检查Subcomponent是否被需要。通过父Component的方法添加Subcomponent表示明确需要它,即使那个方法从没被调用过。Dagger不会检查,因此肯定会生成Subcomponent,即使从来没有使用过它。

7.扩展多元绑定

就像其它依赖关系,Subcomponent可以访问父Component中的多元绑定的,而且还能给那些Map和Set添加元素。这些后加的元素仅对绑定的Subcomponent及其后代可见,不对父Component可见。

@Component(modules = ParentModule.class)interface Parent {  Map<String, Integer> map();  Set<String> set();  Child child(); }@Moduleclass 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(); }@Moduleclass 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");8.重复的模块

当相同的模块类型被添加到Component和它任意Subcomponent中,这些Component将自动使用同一个Module实例。意味以下两种都是错误的,使用重复的Module调用Subcomponent的builder方法,或Subcomponent的工厂方法定义重复的Module作为参数。(前者在编译时不能被检查,易导致运行时错误。)

@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();9.总结

Component之间其实可以通过@Component.dependencies属性添加依赖,这种方式与Subcomponent各有优缺点及使用场景

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持
16人赞 举报
分享到
用户评价(0)

暂无评价,你也可以发布评价哦:)

扫码APP

扫描使用APP

扫码使用

扫描使用小程序