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