Dagger2 依赖的接力游戏(三):Module的使用和原理

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

PS: 本篇文章的示例代码,放在了项目的chapter3分支

上一篇文章我们介绍了使用@inject声明提供类型,并据此实现了Car和Engine的完全依赖注入,这篇文章我们使用Module来实现同样的效果。

我们先梳理一下上篇文章里的依赖关系,我们最后总结了它的依赖模型是这样的:

D(Car,Engine) = D(Car,DaggerCarComponent)

加上Dagger2托管的依赖关系实际上是这样的:

D(Car,Engine) = D(Car,DaggerCarComponent) + D(DaggerCarComponent,Car构造方法) + D(Car构造方法, Engine构造方法)

我们先调整Engine的提供,使用Module来实现。

定义EngineModule

首先我们聊一下什么是Module,Module是Dagger2为我们定义的一个角色,它是一个“提供类型P(?)”的容器,我们用@Module注解一个类来声明这个类是一个module,在这个类里面,所有用@Provides注解的返回类型不为空的方法都是合法的提供类型。要注意的是,Dagger2框架不为Null对象做任何的错误兼容处理,相反地,它会在注入前检查Module创建的对象是否为空,如果为空就报空指针错误,所以我们要确保Module的提供方法返回一个可用的对象。

如果我们想返回可能为空的对象,可以使用@Nullable注解声明,这个注解是类型递归的,使用了这个注解的方法,Component里也必须同样声明,否则会编译失败。

现在我们创建一个EngineModule来实现P(Engine)。

@Module
public class EngineModule {

    @Provides
    Engine provideEngine(){
        return new Engine(2);
    }

}

声明对EngineModule的依赖

创建后我们要告诉CarComponent使用EngineModule来作为它的提供类型的容器,查找它需要的提供类型。Component注解定义了modules索引的class数组参数,用来声明它依赖的所有Module。

这里和使用Inject注解构造方法不同之处在于,类的构造方法总是唯一的(参数类型唯一),因此它作为“提供类型”也是唯一的,所以Dagger2会自动将构造方法声明的提供类型,作为所有Component的备用类型,不用我们手动告诉具体的Component。但是如果在Module里声明提供类型,我们可以在多个Module里声明相同的提供类型,根据不同的使用需求对Component进行装配,因此多了告诉Component它的Modules依赖这一步,这一步也是我们使用Dagger2来管理依赖关系的优越性的体现:我们只要修改注解,就可以修改具体的依赖关系。

我们修改一下CarComponent接口:

@Component(modules = EngineModule.class)
public interface CarComponent {

    void inject(Car car);

    Car getCar();
}

现在我们使用Module声明了Engine的提供类型,main方法的调用方式保持不变:

public class Main {
    
    public static void main(String[] args){
        Car car = DaggerCarComponent.builder().build().getCar();
        System.out.println("cylinderNumbers : " + car.getEngine().getCylinderNumbers());
    }
}

我们运行一下:

cylinderNumbers : 2

Process finished with exit code 0

Engine的气缸数目已经变为了2,也就是说我们修改生效了。

Module的工作机制

现在我们再看看生成的注入代码,了解一下Module的工作机制。我们先看下getCar方法:

  @Override
  public Car getCar() {
    return injectCar(
        Car_Factory.newCar(EngineModule_ProvideEngineFactory.proxyProvideEngine(engineModule)));
  }

对比使用@Inject声明的模板代码,我们发现前半部分

 injectCar(Car_Factory.newCar(xxx));

是一样的,不同的是前者直接创建了一个Engine对象,而这里使用了模板来获取对象,Dagger为我们生成了与Module名称和方法名称对应关系为:

模块名_方法名Factory.proxy方法名(模块名 instance)

的静态类和方法,接受EngineModule对象,并作为它的代理来获取对象,我们看看代理方法:

  public static Engine proxyProvideEngine(EngineModule instance) {
    return Preconditions.checkNotNull(
        instance.provideEngine(), "Cannot return null from a non-@Nullable @Provides method");
  }

这里调用了Module实例中我们自己定义的provideEngine方法,并校验module生成的对象是否为空。我们再看看这个EngineModule实例是怎么初始化的:

    public CarComponent build() {
      if (engineModule == null) {
        this.engineModule = new EngineModule();
      }
      return new DaggerCarComponent(this);
    }

在使用Builder构建CarComponent对象的时候,如果为空就创建一个新的实例,并传递给Component对象。同时我们也看到,在Builder中生成了指定module的方法:

    public Builder engineModule(EngineModule engineModule) {
      this.engineModule = Preconditions.checkNotNull(engineModule);
      return this;
    }

所以我们可以总结一下,如果我们为AComponent实现XModule的YMethod方法,来声明一个P,Dagger为我们做了以下的工作来完成这个功能。

  1. 生成XModule_YMethodFactory.proxyYMethod(XModule instance)的静态代理类和方法,并作为Type实例的获取代理
  2. 在AComponent和AComponent.Builder中生成XModule的成员,并为XModule成员生成建造模式的初始化方法。
  3. 在getType()方法里调用第1步生成的代理方法,获取Type实例作为依赖,创建我们要的目标实例。

这个环节完成的依赖模型替换为:

D(DaggerCarComponent,Engine) = D(DaggerCarComponent,EngineModule_ProvideEngineFactory) + D(EngineModule_ProvideEngineFactory, EngineModule) + D(EngineModule,Engine)

这里值得注意的有以下几点:

  1. 之所以在Builder里生成XModule成员的建造模式方法,是为了提供机制和能力,让我们更加灵活地管理具体的Module,控制对象实例化逻辑。
  2. D(EngineModule,Engine) 是我们手动实现的,是我们真正的业务逻辑,需要我们多加关注,保证逻辑符合预期。

小结

在这一节中,我们使用module来实现了Engine的提供,并通过源码和逻辑模型,分析了module的工作机制以及其背后的设计原理。但是我们看到,作为Car类的组件,依赖了Engine的Module,稍微有点别扭。有时候我们想控制Component、Module的对应关系,比如为了使我们的代码逻辑更加清晰可读,将这种复用的需求,通过组件的复用来实现。下一篇文章,我们将讲解如何复用Component,敬请期待。

你可能感兴趣的:(Dagger2 依赖的接力游戏(三):Module的使用和原理)