翻译Dagger 2使用Subcomponent

作者: lanceJin | 来源:发表于2017-10-14 16:16 被阅读141次

    官方文档链接:https://google.github.io/dagger/subcomponents.html

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

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

    下面的例子中,RootComponent的作用域为@Singleton@SessionScope嵌套在它之中,@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 {...}
    

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

    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各有优缺点及使用场景。由于不是本篇文章的内容,所以不在此处进行比较,感兴趣的朋友可以参考这篇文章,写的很清楚。

    相关文章

      网友评论

        本文标题:翻译Dagger 2使用Subcomponent

        本文链接:https://www.haomeiwen.com/subject/fwzxuxtx.html