轻松学,听说你还没有搞懂 Dagger2

本文旨在用易懂的方式解释 Dagger2,剖析学习难点,如 Java 注解、依赖注入、反射等。Dagger2 是 Google 维护的依赖注入框架,基于 JSR330 注解,简化了依赖注入过程,减少了代码耦合。通过 @Inject、@Component、@Module、@Provides 等注解,Dagger2 实现了依赖的注入和管理。文中还介绍了 Dagger2 中的单例 @Singleton、@Scope、延迟加载、Provider、Component 依赖关系和 SubComponent 等高级特性。通过实例演示了 Dagger2 的使用方法,帮助读者理解其工作原理。
摘要由CSDN通过智能技术生成

Dagger2 确实比较难学,我想每个开发者学习的时候总是经历了一番痛苦的挣扎过程,于是就有了所谓的从入门到放弃之类的玩笑,当然不排除基础好的同学能够一眼看穿。本文的目的尝试用比较容易理解的角度去解释 Dagger2 这样东西。

Dagger2 是有门槛的,这样不同水平能力的开发者去学习这一块的时候,感受到的压力是不一样的。

我个人总结了大家在学习 Dagger2 时,为什么感觉难于理解的一些原因。

  1. 对于 Java 注解内容不熟悉。
  2. 对于依赖注入手段不熟悉。
  3. 对于 Java 反射不熟悉。
  4. 对于 Dagger2 与其它开源库的使用方法的不同之处,没有一个感性的认知。
  5. 对于 Dagger2 中极个别的概念理解不够。
  6. 对于 Dagger2 的用途与意义心生迷惑。

其实以上几点,都可以归类到基础技能不扎实这个范畴内,但正如我所说的,学习 Dagger2 时开发者的水平是不一样的,所以困扰他们的原因就不一样。下面,我针对这些情况,一一给出自己的建议。

对于 Java 注解不熟悉

这一部分的开发者基础知识确实薄弱,那么怎么办呢?当然是学习了。就算不为 Dagger2,注解的知识内容也应该好好值得学习,虽然在平常开发中,我们自己编写注解的机会很少,但是我们运用第三方开源库的时候,应该会经常看见注解的身影,所以熟悉注解不是为了自己编写注解代码,而是为了开发过程中更加高效从容而已。

如果,对 Java 注解一无所知,我可以给大家一个感性的认知。

一般,我们评价某人会说,这是一个好人、坏人、男神、女神、大神、单身狗等等,这是我们人为贴得标签,这些标签有助于我们自己或者其他人去获取被评价的人的基本信息。

而在 Java 软件开发中,我们也可以给某些类,某些字段贴上作用类似的标签,这种标签的名字就叫做注解,只不过这种标签是给代码看的。

这里写图片描述

标签只对特定的人起作用,比如小张被人贴了一个小气鬼的标签,所以小红认为小张是一个小气鬼,但是小张本人不会因为这个标签而改变自己变得不是小张,也许本质上小张是个大方的人。

所以,注解本身也不会影响代码本身的运行,它只会针对特定的代码起到一定的用处,用来处理注解的代码被称作 APT(Annotation Processing Tool)。

更详细的内容请阅读这篇文章《秒懂,Java 注解 (Annotation)你可以这样学》

对依赖注入手段不熟悉

这一块而言,如果让很多人慌张的原因,我觉得可能是依赖注入这个词过于学术化了。而从小到大,10 多年的应试教育让绝大部分的同学对于这些枯燥无味的概念产生了恐惧与绝望。其实,没有那么夸张的,不要被这些东西吓倒。

因为,Java 学习的时候,我们一直写这样的代码。

class B{}


class A {
    B b;

    public A() {
        b = new B();
    }
}

这样的代码,一点问题都没有,类 A 中有一个成员变量 b,b 的类型是类 B。所以,在软件开发中,可以称 A 依赖 B,B 是 A 的依赖,显然,A 可以依赖很多东西,B 也可以依赖很多东西。

通俗地讲,依赖这个概念也没有什么神奇的,只是描述了一种需求关系。

我们再来看一种情况,现在,业务需要,代码越来越复杂。

class B{}

class C{
    int d;
    public C (int value) {
        this.d = value;
    }
}


class A {
    B b;
    C c;

    public A() {
        b = new B();
        c = new C(3);
    }
}

现在,A 有了一个新的依赖 C。不过,由于业务的演进,C 这个类经常发生变化,最明显的变化就是它的构造方法经常变动。

class C{
    int d;
    String e;
    public C (String value) {
        this.e = value;
    }
}


class A {
    B b;
    C c;

    public A() {
        b = new B();
        //c = new C(3);
        c = new C("hello");
    }
}

C 变动的时候,由于 A 依赖于它,A 不得不修改自己的代码。但是,事情还没有完。C 还会变动,C 把 B 也带坏了节奏。

class B{
    int value;

    public B(int value) {
        this.value = value;
    }

}

class C{
    int d;
    String e;
    public C (int index,String value) {
        this.d = index;
        this.e = value;
    }
}


class A {
    B b;
    C c;

    public A() {
        b = new B(110);
//      b = new B();
        //c = new C(3);
//      c = new C("hello");
        c = new C(12,"hello");
    }
}

可以想像的是,只要 B 或者 C 变动一次,A 就可能需要修改自己的代码,用专业术语描绘就是A 与依赖模块太过于耦合,这个可是犯了软件设计的大罪,

我们再可以想像一下,A 是领导,B 和 C 是小兵,如果因为 B 和 C 自身的原因,导致领导 A 一次次地改变自己,那么以现在流行的话来说就是,“你良心不会痛吗?”。所以我们需要的就是进行一些变化来进行解耦,也就是解除这种耦合的关系。让 A 不再关心 B 和 C 的变化,而只要关心自身就好了。

class A {
    B b;
    C c;

    public A(B b, C c) {
        this.b = b;
        this.c = c;
    }

}  

在上面代码中,A 不再直接创建 B 与 C,它把依赖的实例的权力移交到了外部,所以无论 B 和 C 怎么变化,都不再影响 A 了。这种实例化依赖的权力移交模式被称为控制反转(IoC),而这种通过将依赖从构造方法中传入的手段就是被传的神乎其乎的依赖注入(DI)。其实,本质上也没有什么神奇的地方,只是起了一个高大上的名字而已,好比东北的马丽,在国际化上的大舞台,宣称自己是来自神秘东方的 Marry 一样。

依赖注入有 3 种表现形式。
构造方法注入

class A {
    B b;
    C c;

    public A(B b, C c) {
        this.b = b;
        this.c = c;
    }

} 

Setter 注入

class A {
    B b;
    C c;


    public void setB(B b) {
        this.b = b;
    }


    public void setC(C c) {
        this.c = c;
    }

}

接口注入

interface Setter {
    void setB(B b);
    void setC(C c);
}


class A implements Setter{
    B b;
    C c;

    public void setB(B b) {
        this.b = b;
    }


    public void setC(C c) {
        this.c = c;
    }

}

大家肯定会想,依赖注入的引进,使得需求方不需要实例化依赖,但总得有地方去实例化这些依赖啊。确实,依赖注入引进了第三方,你可以称它为 IoC 容器,也可以称它为注射器(injector),为了便于理解,我们之后都有注射器来指代吧,通过注射器可以将依赖以上面 3 种注入方式之一注入到需求方。

这里写图片描述

病人需要的是药水,所以病人是需求者,药水是病人的依赖,注射器把药水注射给病人。

更多细节,请阅读《轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)》

而 Dagger2 就是一个依赖注入框架,你也可以想像它是一位非常智能化的服务员,用来处理大量的顾客的各种订餐需求,然后针对不同的菜单提供给不同的顾客不同类型的餐具。

对于 Java 反射不熟悉

对于这一块不熟悉的同学同样是基础知识太薄弱,需要补强。

相对于正常流程开发,Java 反射是非常规化手段。如果正常流程开发是司机驾驶一辆汽车,那么反射的运用就是采用无人驾驶的手段。

Dagger2 中也应用了反射,不过开发者本身不需要运用反射,Dagger2 是自身框架通过反射处理注解。

学习反射内容可以阅读这篇文章《细说反射,Java 和 Android 开发者必须跨越的坎》

Dagger2 与其它开源库略有不同

开源软件的出现,大大造福了程序员,所以,大家都说不要重复创造轮子

但是,我个人一直认为,不重复创造轮子,不代表可以不去深入了解这些轮子。

我把 Android 开发中所应用到的开源库当作武装

武装与两部分构成,武器装备

那么,在 Android 中什么样的库可以当作是武器呢?什么样的库可以当作是装备呢?

大家想一下,武器什么用途?战斗进行中,用来杀敌的

装备呢?战斗开始时,就要穿上或者安装好的物件。
这里写图片描述

刀、枪、棍、棒是武器,盔甲是装备。
武器拿来就用,盔甲等却要在开始战斗前就装备上。

Java 软件代码是在虚拟机中运行的,所以在这里可以把 jvm 当作战场。

Piccso、Logger、sweet-alert-dialog 等等,这些开源库都是在程序运行过程中拿来就用的。

而 GreenDao、Butterknife、Dagger2 这些因为涉及到了反射处理,而反射处理相对于正常开发速度很慢,所以它们通常在编译时产生一些新的代码,然后才能在程序运行过程中使用,也就是说它们都把反射处理移动到编译器编译代码时的阶段,而程序运行时并不涉及到反射,这就是这些框架运用了反射技术,但是仍然高效的秘诀所在。

所以,Dagger2 会产生中间代码,不少同学应该会有迷惑,为什么引进了 Dagger2 时,要先编译一次代码,不然就会报错。现在,可以解释了,编译代码是为了生成中间代码,然后在中间代码的基础上按照正常的流程开发。

Dagger2 并非横空出世

都说要站在巨人的肩膀上,Dagger2 其实也算站在巨人的肩膀上。

Dagger2 是一款依赖注入的框架,但依赖注入的框架有 ,所以 Dagger2 也并不算是一款新鲜事物,大家觉得新奇不过是因为对于依赖注入框架本身了解过少罢了。

Dagger2 是在 Dagger 的基础上来的,Dagger 是由 Square 公司开发的,Dagger2 基于 Dagger 由 Google 公司开发并维护。
Square 是一家伟大的公司,Android 大神 JakeWoton 之前就在它任职,不久前才离职。而我们熟悉的 RxJava、Butterknife、Retrofit、OKHttp 等等都是 Square 提供的,外号 Square 全家桶。
这里写图片描述

当然,Google 公司更是一家伟大的公司,这个无需多言。

最后,有个重要的地方就是 Dagger2 是基于注解开发的,而 Dagger2 中所涉及到的注解其实是基于 javax.inject 上开发的,它出自 JSR330
这里写图片描述

JSR330 是规范,建议大家怎么做,而 Dagger2 则实现了这个规范。

因此,对于普通开发者而言,学习 Dagger2 其实就是学习相关的注解的意义与用途。

Dagger2 的引进

Dagger2 是适应于 Java 和 Android 开发的依赖注入框架,记住得是它不仅仅对 Android 开发有效。

Dagger2 官网地址是 https://google.github.io/dagger//

对于 Eclipse 开发而言,需要下载相应的 jar 包。

对于 AndroidStudio 开发而言,只需要在相应的 build.gradle 引入对应的依赖就好了。

如果你 AndroidStudio 的 gradle build tool 版本在 2.2 以上,直接在引进就好了

dependencies {
  compile 'com.google.dagger:dagger:2.4'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.4'
}

如果你的 gradle build tool 版本在 2.2 以下,则需要引进 apt 插件。
首先需要在 Project 层级的 build.gradle 文件中引入依赖

buildscript {
    repositories {
      mavenCentral()
    }
    dependencies {
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:2.1.0'
        // the latest version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

然后在 Module 层级的 build.gradle 引入相应的插件和依赖

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'


dependencies {
     apt 'com.squareup.dagger:dagger-compiler:2.4'
     compile 'com.squareup.dagger:dagger:2.4'
     //java注解
     compile 'org.glassfish:javax.annotation:10.0-b28'
}

Dagger2 的基本概念

前面讲到过 Dagger2 基于 JSR330 注解,在普通开发者视角中,就是这些注解构成了 Dagger2 的全部。

前面文章我提到过,注解如同标签,给一个人贴标签有助于自己去理解这个人,而给代码贴标签,有助于 APT 程序去处理相应的代码,Dagger2 有自己的注解,而这些注解也有特定的意义,它们大体上都是为了实现依赖注入。

我们说依赖注入有 3 种手段:
- 构造方法注入
- Setter 注入
- 接口注入

但是,如果不借助于框架的话,我们就必须自己编写相应的代码,这些代码充当了注射器的角色。

B b = new B(5);
C c = new C(110,"110");

A a = new A();
a.setB(b);
a.setC(c);

A 将内部的依赖 B 和 C 的实例化的权力移交到了外部,通过外部的注入。

Dagger2 这类依赖注入框架的出现进一步解放了我们的双手,Dagger2 有一套自己的依赖注入机制,我们不再手动编写注射器,而只要按照规则配置好相应的代码就好了,Dagger2 会自动帮我们生成注射器,然后在适当的时候进行依赖注入。

什么意思呢?意思就是我们不需要调用 a.setB() 和 a.setC() 方法,只需对代码添加一些注解就好了。

class B{
    int value;
    @Inject
    public B(int value) {
        this.value = value;
    }

}

class C{
    int d;
    String e;
    @Inject
    public C (int index,String value) {
        this.d = index;
        this.e = value;
    }
}

class A {
    @Inject
    B b;
    @Inject
    C c;
}

看起来,不可思议,不是吗?@Inject 是一个注解,只要按照 Dagger2 的配置,就能颠覆我们之前的编码习惯。

但不管看起来怎么神奇,任何事都有一个本质。

因此这个本质就是,Dagger2 是一个依赖注入框架,依赖注入的目的就是为了给需求方在合适的时候注入依赖。

对 Dagger2 学习过程如果感到不适与难以理解,回过头来想想它的本质好了。

这里写图片描述
Dagger2 的使命就是为了给需求者注射依赖。

@Inject 注解就如同一个标签,或者说它是一个记号,它是给 Dagger2 看的。它运用的地方有两处。

  1. @Inject 给一个类的相应的属性做标记时,说明了它是一个依赖需求方,需要一些依赖。

  2. @Inject 给一个类的构造方法进行注解时,表明了它能提供依赖的能力。

就这样,通过 @Inject 注解符号,就很容易标记依赖和它的需求方。但是,单单一个 @Inject 是不能让 Dagger2 正常运行的。还需要另外一个注解配合。这个注解就是 @Component。

而 @Component 相当于联系纽带,将 @inject 标记的需求方和依赖绑定起来,并建立了联系,而 Dagger2 在编译代码时会依靠这种关系来进行对应的依赖注入。

@Inject 和 @Component

我们来编写代码,验证一下。

假设有这么一个场景:

一个宅男,他喜欢在家玩游戏,所以饿了的时候,他不想自己煮饭吃,也不愿意下楼去餐厅,他选择了外卖。

public class ZhaiNan {
   

    @Inject
    Baozi baozi;

    @Inject
    Noodle noodle;

    @Inject
    public ZhaiNan() {

    }

    public String eat() {
        StringBuilder sb = new StringBuilder();
        sb.append("我吃的是 ");
        if ( baozi != null ) {
            sb.append(baozi.toString());
        }

        if (noodle != null) {
            sb.append("  ");
            sb.append(noodle.toString());
        }
        return sb.toString();
    }
}

public class Baozi {
   

    @Inject
    public Baozi() {
    }

    @Override
    public String toString() {
        return "小笼包";
    }
}

publi
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

frank909

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值