Dagger2 依赖的接力游戏(四):Component的复用
上一篇文章我们使用CarComponent依赖EngineModule讲解了Module的使用和原理,并引出了Component的复用问题。现在我们通过CarComponent复用EngineComponent来实现Engine的注入。
Component复用有两种形式,一种是使用dependencies参数声明它的依赖组件,一个是用subcomponent声明它的子组件,这两个参数接受的都是class数组。Dagger2要求我们手动声明Component之间的依赖关系的原因,和上面说的声明Module依赖的原因是一样的,就是为了提供机制,让我们可以灵活地调整我们的依赖关系。第一种比较简单直观,我们先用第一种来实现。
使用组件依赖实现注入
PS: 本节的示例代码收录在项目的chapter4.1分支
我们修改EngineComponent,让它依赖EngineModule:
@Component(modules = EngineModule.class)
public interface EngineComponent {
Engine getEngine();
}
然后我们再修改CarComponent的依赖:
@Component(dependencies = EngineComponent.class)
public interface CarComponent {
void inject(Car car);
Car getCar();
}
main的方法也保持不变,我们这里也贴一下便于理解:
public class Main {
public static void main(String[] args){
Car car = DaggerCarComponent.builder().build().getCar();
System.out.println("cylinderNumbers : " + car.getEngine().getCylinderNumbers());
}
}
我们运行一下,发现报错了:
Exception in thread "main" java.lang.IllegalStateException: com.example.dagger2.EngineComponent must be set
at com.example.dagger2.DaggerCarComponent$Builder.build(DaggerCarComponent.java:52)
at com.example.dagger2.Main.main(Main.java:12)
我们查看一下报错的代码:
public final class DaggerCarComponent implements CarComponent {
//...此处省略N行代码
public static final class Builder {
//...此处省略N行代码
public Builder engineComponent(EngineComponent engineComponent) {
//...此处报错了
this.engineComponent = Preconditions.checkNotNull(engineComponent);
return this;
}
}
}
如果我们稍微跟踪一下代码逻辑,会发现DaggerCarComponent.Builder没有为我们创建EngineCarComponent对象,而是直接检查这个对象是否未空,所以我们要在main方法里手动创建,并传递给它,我们修改一下main方法:
public class Main {
public static void main(String[] args){
EngineComponent engineComponent = DaggerEngineComponent.create();
Car car = DaggerCarComponent.builder().engineComponent(engineComponent).build().getCar();
System.out.println("cylinderNumbers : " + car.getEngine().getCylinderNumbers());
}
}
我们运行一下:
cylinderNumbers : 2
Process finished with exit code 0
结果是正确的了。从这里我们可以看出来如果使用组件依赖的方式来满足需求,实际上使用的还是两个独立的组件,只不过依赖组件需要被依赖组件的实例才能正常工作。Dagger2会为被依赖的组件创建完整的注入模板,因此也会在编译时检查被依赖组件依赖关系的完整性。被依赖组件的实例需要我们自己维护,依赖组件只负责使用创建好的被依赖组件的实例。我们再使用子组件来实现EngineComponent的依赖。
使用子组件实现依赖注入
PS: 本节的示例代码收录在项目的chapter4.2分支
在示例开始之前,我们要讲一下什么是子组件,讲清楚这个概念是理解我们示例的关键。我自己在学习的时候,就子组件这里卡了很久才慢慢摸清楚到底要怎么用它,dagger对它到底有哪些支持和限制。所以这个部分非常重要,请慢下来仔细看。
我们在第二篇中解释了,组件是依赖关系的容器。在上一小结的组件依赖示例中,我们演示了CarComponent依赖EngineComponent的工作模式,如果我们用集合C(collection)表示它们的容器情况,是这样的:
C(EngineComponent) = { P(Engine) }
C(CarComponent) = {P(Car), P(Engine), I(Car,Engine)}
C(EngineComponent) 和C(CarComponent)都是完整的依赖集合,其中C(CarComponent)中P(Engine)是通过组件依赖,由EngineModule提供的,也就是说EngineComponent作为CarComponent的一部分,才满足了CarComponent的依赖完整性,只是在生成的注入模板之后,需要我们独立维护实例。
而我一开始看到子组件的时候,以为子组件就是这么回事!!!
实际上子组件的定义刚好和这个相反,如果我们把EngineComponent定义成CarComponent的子组件,容器情况是这样的:
C(CarComponent) = {P(Car), (Car,Engine)}
C(EngineComponent) = {P(Car), I(Car,Engine), P(Engine)}
看懂了吗?子组件继承了父组件里所有的依赖关系,父组件才是它的一个子集。父组件的依赖关系是完整的,而子组件在定义的时候依赖关系可以是不完整的,在绑定到父组件的时候再补齐依赖关系。但是我们在使用的时候,要通过父组件里的接口暴露子组件来使用的。这样的一个依赖和使用的反差,带来的好处有两个:
- 父组件无法使用子组件的依赖关系,子组件对自己的依赖关系可以起到封装和保护的作用。
- 父组件是依赖完整的,可以独立于子组件存在,因此子组件可以有独立的生命周期,这也是子组件设计的目的。
了解了这个基础之后,我再看看依赖关系。Car类依赖Engine类,Engine类是可以独立存在的。因此我们在设计的时候应该调整父子组件的关系,CarComponent是子组件,EngineComponent是父组件。为了完整演示一个子组件,我们用CarModule来实现P(Car):
@Module
public class CarModule {
@Provides
Car provideCar(Engine engine){
return new Car(engine);
}
}
我们再看看子组件怎么声明:
//子组件声明注解,参数及作用和组件一模一样
@Subcomponent(modules = CarModule.class)
public interface CarComponent {
Car getCar();
//子组件必须要有一个Buidler声明,否则父组件不知道怎么构建子组件
@Subcomponent.Builder
interface Builder{
//可选的方法,不过如果不声明,就没法指定子组件的Module
Builder carModule(CarModule module);
//创建子组件的方法,必须声明
CarComponent build();
}
}
可以看到我们子组件的依赖是不完整的,缺少了P(Engine)。
子组件要绑定到父组件,要通过module来实现:
//将子组件绑定到module
@Module(subcomponents = CarComponent.class)
public class EngineModule {
@Provides
Engine provideEngine(){
return new Engine(2);
}
}
接下来我们要在父组件里暴露子组件的Builder,否则虽然依赖关系是满足了,但是无法使用:
@Component(modules = EngineModule.class)
public interface EngineComponent {
Engine getEngine();
//通过Builder暴露子组件
CarComponent.Builder carComponent();
}
然后我们在main方法里修改一下调用方式:
public class Main {
public static void main(String[] args){
Car car = DaggerEngineComponent.builder()
.engineModule(new EngineModule())
.build()
.carComponent()
.carModule(new CarModule())
.build().getCar();
System.out.println("cylinderNumbers : " + car.getEngine().getCylinderNumbers());
}
}
运行一下结果是正确的。
cylinderNumbers : 2
Process finished with exit code 0
子组件的几个定义很容易写懵逼,没有仔细琢磨过简直不理解为什么dagger要我们手动写这么多东西。为了方便理解,我把注意点全部写在了注释中,紧跟代码,方便对照。这个小节强烈建议大家实践一下,然后回头再看前面子组件的定义,才能更好地理解依赖的继承关系。
通过调用方式我们可以看到,子组件是依赖于父组件才能进行工作的,它并不会被独立的编译成注入代码,而是通过内部类的方式来实现子组件接口,外部是无法实例化子组件的对象的,通过这种方式,来保证子组件对父组件的依赖性。
小结
本篇我们使用组件复用的方式,实现了Engine的依赖注入。然后通过逻辑模型,分析了其背后的复用逻辑。源码分析这里就不在做了,读者可以跟自己查看一下源码,结合设计目的,理解一下为什么这么实现。截止目前为止,我们都是采用不同的方式,实现相同的Engine注入效果。这是最简单的依赖关系,下一篇开始,我们开始讲解复杂的依赖需求,包括使用相同返回类型相同,但是实例需求不同,包括实例的作用域控制等等。敬请期待。
参考文档
知乎: 神兵利器dagger2
github : Dagger2官方入口
Dagger 2 for Android Beginners
Dagger2入门解析