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<Type>,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,敬请期待。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容