Dagger2 依赖的接力游戏(四):Component的复用

文接Dagger2 依赖的接力游戏(三)

上一篇文章我们使用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)}

看懂了吗?子组件继承了父组件里所有的依赖关系,父组件才是它的一个子集。父组件的依赖关系是完整的,而子组件在定义的时候依赖关系可以是不完整的,在绑定到父组件的时候再补齐依赖关系。但是我们在使用的时候,要通过父组件里的接口暴露子组件来使用的。这样的一个依赖和使用的反差,带来的好处有两个:

  1. 父组件无法使用子组件的依赖关系,子组件对自己的依赖关系可以起到封装和保护的作用。
  2. 父组件是依赖完整的,可以独立于子组件存在,因此子组件可以有独立的生命周期,这也是子组件设计的目的。

了解了这个基础之后,我再看看依赖关系。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入门解析

你可能感兴趣的:(Dagger2 依赖的接力游戏(四):Component的复用)