在上一个文章中,我们介绍了 Dagger 中的限定和范围注解,现在我们将视线转移到依赖注入器来,先介绍这个组件的依赖的两种方式,再介绍两个常用的类型。

强烈建议先看完上一个文章:这可能是最详细的 Dagger2 使用教程 二(限定注解 @Named、@Qulifier 和 范围注解 @Singleton、@Scope)

让依赖注入器依赖其他依赖注入器(@Component中的 dependencies 属性)

在实际生活中,一个供应商基本是无法完成所有物品的供应的,但是为了保证商品的尽可能完整,它们可能会依赖其他的供应商。例如在淘宝中,它能供应很多东西,但是有些东西它还是需要找其他供应商的。例如一台电脑中的 CPU,这玩意全球也就两家能生产,所以就是依赖于AMD 和 Intel,有人买的时候,淘宝也是找这两家店拿货。

在本文的例子中,张三从 Taobao 买电脑,其实 CPU 也是要从其他供应商拿的。现在我们就通过这个例子来修改一下我们的代码。

但是在修改之前我们得先思考这样的一个问题,在面向对象的思想中,针对这样的场景,我们往往会抽取父类,使用继承来做,使淘宝这个供应商继承 CPU 供应商,这样淘宝就有了提供 CPU 的能力了。
不过这是在我们普通编程中,在 Dagger 中,就必须使用注入器的依赖,即 @Componentdependencies 属性。我们先将 getCPU 的方法从 Taobao 抽离出来,放到一个 CPUProvider 中:

@Module
public class CPUProvider {

    @Provides
    @Named("AMD")
    public CPU getAMDCPU() {
        return new CPU("AMD");
    }

    @Provides
    @Named("Intel")
    public CPU getIntelCPU() {
        return new CPU("Intel");
    }
}

CPU 供应商能够提供两种类型的 CPU:AMD 的和 Intel 的,但要想从供应商拿货,还需要一个依赖注入器,类似于中通快递。由于 CPU 也就美国这两个公司能生产,那这个专门用于配送 CPU 的注入器我们就叫他 UPS 吧:

@Component(modules = CPUProvider.class)
public interface UPSExpress {
    @Named("AMD") CPU getAMDCPU();
    @Named("Intel") CPU getIntelCPU();
}

注意,对于一般的依赖注入器,我们会定义类似于 injectTo 这样的函数。但是这个依赖注入器是给其他依赖注入器使用的,因此不需要 inject 方法。定义的方法的返回值需要是这个注入器能够提供的类型,否则依赖它的依赖注入器就无法找到这个类型。现在还需要修改一下 ZTOExpress 这个接口,为其添加 dependencies 这个方法:

@SanScope
@Component(modules = {TaoBao.class}, dependencies = UPSExpress.class)
public interface ZTOExpress {
    void deliverTo(Person person);
}

好了,现在咱们就可以使用 ZTOExpressPerson 对象进行依赖注入了。也许你不太懂为什么会有依赖注入器还需要依赖另一个依赖注入器这种情况,可以联想一下跨境电商的配送的过程,一个东西从国外的商店买过来首先要经过国外的快递公司,到了海关,再由国内快递公司接手。这不是就通了。

在进行依赖注入时,还需要修改一点代码:

Person person = new Person("张三");
UPSExpress upsExpress = DaggerUPSExpress.builder().cPUProvider(new CPUProvider()).build();
ZTOExpress ztoExpress = DaggerZTOExpress.builder().taoBao(new TaoBao()).uPSExpress(upsExpress).build();
ztoExpress.deliverTo(person);
person.playGame("赛博朋克2077");

这里创建了 UPSExpress 的实例,并且将其传入到 ZTOExpress 实例中,然后 ZTOExpress 还能够完成注入。

另外,看一看 Dagger 生成的这函数的名字,cPUProvideruPSExpress,真是不忍直视,吐槽一下。最后,看下运行结果:

System.out                I  张三
System.out                I      使用 淘宝的台式机(AMD CPU,希捷 硬盘 @86589c2) 玩     赛博朋克2077
System.out                I      使用 淘宝的笔记本(Intel CPU,希捷 硬盘 @86589c2) 玩     赛博朋克2077

最后提一下,在我们正常的开发过程中,像 UPSExpress 这种其他具体注入器可能会依赖的注入器,一般都会提前在某个地方以单例的方式创建好,当要创建具体注入器时候,再将其设置到具体的注入器中。例如,在 Application 类的 onCreate 方法中创建并提供 get 方法。
这可能是本年度最好用的 Dagger2 使用教程 三(依赖注入器的依赖、子组件、Lazy、Provider)-LMLPHP

使用 @SubComponent 定义子组件、即子依赖注入器

对于上面的场景,中通能够完成对张三的配送,但是在现实生活中,中通这么大的公司,在全国是有很多分部的,真实的配送任务是会被分配给这些地区部门的。一个部门大了,就会有子部门,放到依赖注入器中,如果这个依赖注入器太复杂,那就应该划分为若干个子的依赖注入器,这就要用到 @SubComponent 这个注解了。

我们现在假设张三是住在大上海的,那么为张三进行配送的,肯定是中通的上海分部。咱们就先定义一个中通的上海分部:

@Subcomponent
public interface ZTOShanghaiExpress {
    void deliverTo(Person person);
}

这个上海分部是属于中通的,那么在使用的时候,我们肯定是通过本部才能拿到分部的实例再使用的,也就是说,在 ZTOExpress 中应该有一个返回上海分部的方法:

@SanScope
@Component(modules = {TaoBao.class}, dependencies = UPSExpress.class)
public interface ZTOExpress {
    void deliverTo(Person person);
    ZTOShanghaiExpress getShanghaiDepartment();
}

在使用的时候,我们就通过 getShanghaiDepartment 这个方法返回的 ZTOShanghaiExpress 来进行注入,由于不用总部进行配送,因此 ZTOExpress.deliverTo 这个方法其实是可以删除的。

注入的代码如下:

Person person = new Person("张三");
UPSExpress upsExpress = DaggerUPSExpress.builder().cPUProvider(new CPUProvider()).build();
ZTOExpress ztoExpress = DaggerZTOExpress.builder().taoBao(new TaoBao()).uPSExpress(upsExpress).build();
ZTOShanghaiExpress ztoShanghaiExpress = ztoExpress.getShanghaiDepartment();       //重点
ztoShanghaiExpress.deliverTo(person);
person.playGame("赛博朋克2077");

这里可以看到是通过 ZTOExpressgetShanghaiDepartment 获取到一个 ZTOShanghaiExpress 实例,然后再惊醒注入操作。

这里与 @Component 不同的是,这个 ZTOShanghaiExpress 没有为其设置 modules 属性,也没有设置 @Scope 注解,那它是怎么通过编译的呢?这其实是因为当它属于某个依赖注入器时,会继承其父组件的注解。此处,ZTOShanghaiExpress 就继承了 ZTOExpress@SanScope@Component(modules = {TaoBao.class}, dependencies = UPSExpress.class)
这可能是本年度最好用的 Dagger2 使用教程 三(依赖注入器的依赖、子组件、Lazy、Provider)-LMLPHP

延迟加载 Lazy 和 强制重新加载 Provider

在 Dagger 中, LazyProvider 都是用于包装需要被 Dagger 注入的类型,这就有点像 Java 中的 WeakReference。其中 Lazy 用于延迟加载,所谓的懒加载就是当你需要用到该依赖对象时,Dagger 才帮你去获取一个;Provide 用于强制重新加载,也就是每一要用到依赖对象时,Dagger 都会帮你依赖注入一次。

这里我们修改一下 Person 对象,使其 computerLazy 包装,用到时再初始化;另外再添加一个 cola 的对象,每次用到时,都应该返回一瓶新的可乐:

public class Person {

    @Inject
    @DesktopComputer
    Lazy<Computer> desktop;

    @Inject
    @NotebookComputer
    Lazy<Computer> notebook;

    @Inject
    Provider<Cola> cola;

    public void playGame(String gameName) {
        System.out.print(name + "\n");
        desktop.get().play("\t" + gameName);
        notebook.get().play("\t" + gameName);

        System.out.println("\t 喝了一瓶可乐:"+cola.get());
        System.out.println("\t 再了一瓶可乐:"+cola.get());
        System.out.println("\t 还了一瓶可乐:"+cola.get());
    }
    
    //......
}

然后我们让 TaoBao 这个依赖供应商提供能够提供 Cola,一个普通的 @Provides 方法:

@Module
public class TaoBao {

    @Provides
    public Cola getCola() {
        System.out.println("TaoBao 获取一瓶可乐");
        return new Cola();
    }
    
    //......
}

其他的地方不用更改,我们重新跑一下:

System.out                I  张三
System.out                I  TaoBao 创建台式机
System.out                I  使用 淘宝的台式机(AMD CPU,希捷 硬盘 @bb30609) 玩     赛博朋克2077
System.out                I  TaoBao 创建笔记本
System.out                I  使用 淘宝的笔记本(Intel CPU,希捷 硬盘 @bb30609) 玩     赛博朋克2077
System.out                I  TaoBao 获取一瓶可乐
System.out                I       喝了一瓶可乐:lic.swift.demo.dagger.Cola@b06800e
System.out                I  TaoBao 获取一瓶可乐
System.out                I       再了一瓶可乐:lic.swift.demo.dagger.Cola@59cfa2f
System.out                I  TaoBao 获取一瓶可乐
System.out                I       还了一瓶可乐:lic.swift.demo.dagger.Cola@76b083c

可见 computecola 都是使用时才去调用的,只是 getCola 调用了多次。
如果你使用 Dagger 的话,那么这两个类型肯定会比较常用,不过还好这两个类型都不难,使用过 WeakReference 就知道这两个类型也就是相同的用法。
这可能是本年度最好用的 Dagger2 使用教程 三(依赖注入器的依赖、子组件、Lazy、Provider)-LMLPHP

07-17 00:24