Dagger2 确实比较难学,我想每个开发者学习的时候总是经历了一番痛苦的挣扎过程,于是就有了所谓的从入门到放弃之类的玩笑,当然不排除基础好的同学能够一眼看穿。本文的目的尝试用比较容易理解的角度去解释 Dagger2 这样东西。
Dagger2 是有门槛的,这样不同水平能力的开发者去学习这一块的时候,感受到的压力是不一样的。
我个人总结了大家在学习 Dagger2 时,为什么感觉难于理解的一些原因。
- 对于 Java 注解内容不熟悉。
- 对于依赖注入手段不熟悉。
- 对于 Java 反射不熟悉。
- 对于 Dagger2 与其它开源库的使用方法的不同之处,没有一个感性的认知。
- 对于 Dagger2 中极个别的概念理解不够。
- 对于 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 一样。
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 也并不算是一款新鲜事物,大家觉得新奇不过是因为对于依赖注入框架本身了解过少罢了。
当然,Google 公司更是一家伟大的公司,这个无需多言。
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' }
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 有自己的注解,而这些注解也有特定的意义,它们大体上都是为了实现依赖注入。
但是,如果不借助于框架的话,我们就必须自己编写相应的代码,这些代码充当了注射器的角色。
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 看的。它运用的地方有两处。
- @Inject 给一个类的相应的属性做标记时,说明了它是一个依赖需求方,需要一些依赖。
- @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 "小笼包"; } } public class Noodle {
@Inject public Noodle() { } @Override public String toString() { return "面条"; } }
上面代码可以看到,@Inject 注解的身影,需求方是 ZhaiNan 这个类,而 Baozi 和 Noodle 是它的依赖。前面说过,光有 @Inject 的话还不行,需要 @Component 配合。
@Component 怎么使用呢?
很简单,它只需要注解在一个接口上就好了。
@Component() public interface Platform {
ZhaiNan waimai(); }
Platform 是一个接口,它代表着外卖平台,它内部有一个 waimai() 的方法,返回 ZhaiNan 的类型。
这个接口特别的地方就是它的方法中的返回类型。如果一个方法返回了一个类型,那么其实也算是一种依赖的提供,我们可以在后续的代码中感受。
既然是接口,那么它就需要实现类,但是 Dagger2 会自动帮我们生成一个实现类,前提是使用这个类的时候,要先对工程进行编译。前面用装备解释过 Dagger2 这种类型的库,它会在编译阶段产生中间代码,这些中间代码就包括自动实现了被 @Component 注解过的接口实现类。
所以,我们如果要使用 Dagger2 为了我们自动生成的类时,我们就应该先 Build->Make Project 编译一次代码。生成的代码位置在 app 模块 build 文件夹中,在 AndroidStudio 切换 Project 视角就可以看到。

这个目录下都是 Dagger2 产生的中间产物,DaggerPlatform 就是 Dagger2 为我们自动实现的 Platform 这个接口的实现类,注意它的名字都是 Dagger+接口名称。
有了 DaggerPlatform,我们就能够使用 Dagger2 进行代码的依赖注入了。
public class MainActivity extends AppCompatActivity {
Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.btn_test); final ZhaiNan zainan = DaggerPlatform.builder() .build() .waimai(); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,zainan.eat(),Toast.LENGTH_LONG).show(); } }); } }
需要注意的地方是,Component 的实现类是由 Dagger2 自动生成的,它的名字前面说了是 Dagger+接口名称。但这是通常情况,因为 @Component 注解的都是顶级类。但还有一种情况是。
class Foo { static class Bar { @Component interface BazComponent {} } }
它只是一个内部类的接口,Dagger2 针对这种情况需要把外部的类的名字加下划线的形式拼接起来,所以上例中 Dagger2 生成的 Component 实现类类名是 DaggerFoo_Bar_BazComponent。
我们并没有在任何地方用 new 关键字亲自创建 ZhaiNan 这个类的实例,但是它确实有效,而且它的内部依赖 Baozi 和 Noodle 都被实例化了,也就是说依赖被正确地注入到了 ZhaiNan 的实例对象当中。
所以,@Component 和 @Inject 的配合就能够使用 Dagger2 了,但这里面存在一个局限,@Inject 只能标记在我们自己编写的类的构造方法中,如果我们使用第三方的库或者标准库的话,是不是代表我们对于这些就无能为力了呢?
答案显然是否定的,Dagger2 作为一款优秀的框架必须考虑到开发过程中的方方面面,不然谈何优秀呢?
Dagger2 为了能够对第三方库中的类进行依赖注入,提供了 @Provides 和 @Module 两个注解。
@Provides 和 @Module
@Module public class ShangjiaAModule {
@Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } @Provides public Noodle provideNoodle() { return new Noodle(); } } public class Baozi {
String name; @Inject public Baozi() { name = "小笼包"; } public Baozi(String name) { this.name = name; } @Override public String toString() { return name; } }
前面有讲过,@Component 是依赖双方的联系纽带,现在多了一个 @Module 注解,怎么配合使用呢?方法,很简单。只要在 @component 注解后面的括号中取值就是。
@Component(modules = ShangjiaAModule.class) public interface WaimaiPingTai {
ZhaiNan waimai(); }
然后编写测试代码
mBtnTestModule = (Button) findViewById(R.id.btn_test_module); final ZhaiNan zainan1 = DaggerWaimaiPingTai.builder() .build() .waimai(); mBtnTestModule.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,zainan1.eat(),Toast.LENGTH_LONG).show(); } });
我们再看看 @Provides 用法。
@Module public class ShangjiaAModule {
@Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } @Provides public Noodle provideNoodle() { return new Noodle(); } }
@Provides 注解的方法中直接用 new 创建了依赖,其实还有另外一种方式。我们先对 Noodle 进行重构,让它作为面条的基类,然后编写它一个继承类。
public class Noodle {
@Inject public Noodle() { } } public class Tongyi extends Noodle{
@Inject public Tongyi() { } @Override public String toString() { return "统一方便面"; } }
ZhanNan 这个类不用改变,然后,用另外一种方式编写 @Provides 注解的方法。
@Module public class ShangjiaAModule {
@Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } @Provides public Noodle provideNoodle(Tongyi noodle) { return noodle; } }
@Provides public Noodle provideNoodle(Tongyi noodle) { return noodle; } @Provides public Noodle provideNoodle(Tongyi noodle) { return noodle; }
public class Kangshifu extends Noodle{
public Kangshifu() { } @Override public String toString() { return "康师傅方便面"; } }
@Module public class ShangjiaAModule {
@Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } @Provides public Noodle provideNoodle(Kangshifu noodle) { return noodle; } // @Provides // public Noodle provideNoodle(Tongyi noodle) {
// return noodle; // } }
再进行编译的时候,会发现 IDE 报错了。
Error:(10, 13) 错误: com.frank.dagger2demo.Kangshifu cannot be provided without an @Inject constructor or from an @Provides-annotated method. com.frank.dagger2demo.Kangshifu is injected at com.frank.dagger2demo.ShangjiaAModule.provideNoodle(noodle) com.frank.dagger2demo.Noodle is injected at com.frank.dagger2demo.ZhaiNan.noodle com.frank.dagger2demo.ZhaiNan is provided at com.frank.dagger2demo.WaimaiPingTai.waimai()
Log 提示的错误信息是 Kangshifu 这个类代码中没有被 @Inject 注解过的构造方法,也没有办法从一个被 @Provides 注解过的方法中获取。
所以,什么时候用 new 创建对象,什么时候可以直接返回传入的参数就很明显了。对于被 @Inject 注解过构造方法或者在一个 Module 中的被 @Provides 注解的方法提供了依赖时,就可以直接返回传入的参数,而第三方的库或者 SDK 自带的类就必须手动创建了。
@Module public class ShangjiaAModule {
@Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } @Provides public Kangshifu provideKangshifu() { return new Kangshifu(); } // @Provides // public Noodle provideNoodle(Tongyi noodle) {
// return noodle; // } }
现在,我有一个新的需求更改。我在 ZhaiNan 类中 eat() 方法中要把餐厅名字打印出来。
public class ZhaiNan {
@Inject Baozi baozi; @Inject Noodle noodle; @Inject public ZhaiNan() { } @Inject String resturant; public String eat() { StringBuilder sb = new StringBuilder(); sb.append("我从 "); sb.append(resturant.toString()); sb.append("订的外卖,"); sb.append("我吃的是 "); if ( baozi != null ) { sb.append(baozi.toString()); } if (noodle != null) { sb.append(" "); sb.append(noodle.toString()); } return sb.toString(); } }
@Module public class ShangjiaAModule {
@Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } @Provides public Noodle provideNoodle(Kangshifu noodle) { return noodle; } @Provides public Kangshifu provideKangshifu() { return new Kangshifu(); } @Provides public String provideResturant() { return "王小二包子店"; } // @Provides // public Noodle provideNoodle(Tongyi noodle) {
// return noodle; // } }
Error:(10, 13) 错误: java.lang.String cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method. java.lang.String is injected at com.frank.dagger2demo.ZhaiNan.resturant com.frank.dagger2demo.ZhaiNan is provided at com.frank.dagger2demo.Platform.waimai()
现在,我把代码再重构。在 ShangjiaAModule 中提供餐厅名字的时候,直接返回了“王小二包子店”,这个过于直接,缺少变动,现在针对这个进行变化。
@Module public class ShangjiaAModule {
String restaurant; public ShangjiaAModule(String restaurant) { this.restaurant = restaurant; } ...... @Provides public String provideResturant() { return restaurant; } }
编译没有出错,但运行的时候出错了。
Caused by: java.lang.IllegalStateException: com.frank.dagger2demo.ShangjiaAModule must be set at com.frank.dagger2demo.DaggerPlatform$Builder.build(DaggerPlatform.java:64) at com.frank.dagger2demo.MainActivity.onCreate(MainActivity.java:21)
Log 提示的是调用 DaggerPlatform中的Builder.build() 方法时出错了,因为 ShangjiaAModule 并没有出错。我们定位代码。
final ZhaiNan zainan = DaggerPlatform.builder() .build() .waimai();
我之前没有讲的是,如果一个 Module 没有实现任何构造方法,那么在 Component 中 Dagger2 会自动创建,如果这个 Module 实现了有参的构造方法,那么它需要在 Component 构建的时候手动传递进去。怎么传呢?Component 中生成的 Builder 构造器有与 Module 名字相同的方法,并且参数类型就是 Module 类型。大家细细体会下面代码就明白了。
final ZhaiNan zainan = DaggerPlatform.builder() .shangjiaAModule(new ShangjiaAModule("王小二包子店")) .build() .waimai(); final ZhaiNan zainan1 = DaggerWaimaiPingTai.builder() .shangjiaAModule(new ShangjiaAModule("衡阳鱼粉店")) .build() .waimai();
另外,还有一种特殊情况就是,像在 Android 中,MainActivity 这样的代码是我们自己编写的,所以我们可以给相应的属性添加 @Inject 注解,但是 MainActivity 对象的创建却是由 Android Framework 框架决定的,那么,Dagger2 有没有针对这种内部拥有 @Inject 标注的属性,但还没有进行依赖绑定的类的对象进行依赖注入呢?答案是肯定的。
我们知道,Component 是一个接口,它里面可以定义很多方法。方法的返回值可以提供一种类型的对象,前提是这个类的对象被 @Inject 注解过构造方法或者在 Module 中被 @Provides 注解过的方法提供。
@Component(modules = ShangjiaAModule.class) public interface WaimaiPingTai {
ZhaiNan waimai(); }
ZhaiNan 能够在一个 Component 中的方法中作为类型返回是因为它符合我上面说的条件,它的构造方法被 @Inject 注解过。
需要注意的是,Component 中方法除了可以返回类型,还可以在方法中传入类型参数。目的是针对这个参数对象进行依赖注入。
比如
@Component(modules = ShangjiaAModule.class) public interface WaimaiPingTai {
ZhaiNan waimai(); void zhuru(ZhaiNan zhaiNan); }
我新增了一个方法,zhuru() 中的参数就是 ZhaiNan 类型,代表 DaggerWaimaiPingTai 调用这个方法时能够对一个 ZhaiNan 对象进行依赖注入。
可以编写代码验证。
mBtnTestZhuru = (Button) findViewById(R.id.btn_test_zhuru); final ZhaiNan zhaiNan = new ZhaiNan(); WaimaiPingTai daggerWaimaiPingTai = DaggerWaimaiPingTai.builder() .shangjiaAModule(new ShangjiaAModule("常德津市牛肉粉")) .build(); // 通过调用接口中的方法给 zhaiNan 进行依赖注入 daggerWaimaiPingTai.zhuru(zhaiNan); mBtnTestZhuru.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,zhaiNan.eat(),Toast.LENGTH_LONG).show(); } });
所以,我们可以给接口方法参数传值的形式来给 Activity 进行依赖注入。
@Module public class ActivityModule {
@Provides public int provideActivityTest(){ return ; } } @Component(modules = {ShangjiaAModule.class,ActivityModule.class}) public interface WaimaiPingTai {
ZhaiNan waimai(); void zhuru(ZhaiNan zhaiNan); void inject(MainActivity mainActivity); }
我们编写了新的 Module,然后把它放时 WaimaiPingTai 这个 Component 中去,再添加了 inject() 方法,为的是能够给 MainActivity 实例进行依赖注入。
现在我们添加测试代码,首先在 MainActivity 中添加一个 int 类型的成员变量。
@Inject int testvalue;
然后要调用相关注入方法
mBtnTestActivity = (Button) findViewById(R.id.btn_test_inject_act); final ZhaiNan zhaiNan = new ZhaiNan(); WaimaiPingTai daggerWaimaiPingTai = DaggerWaimaiPingTai.builder() .shangjiaAModule(new ShangjiaAModule("常德津市牛肉粉")) .build(); daggerWaimaiPingTai.inject(this); mBtnTestActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,"testvalue is "+ testvalue,Toast.LENGTH_LONG).show(); } });
Component 的创建方式
我们可以看到,创建 Component 都是通过它的 Builder 这个类来进行构建的。其实还有另外一种方式。那就是直接调用 Component 实现类的 create() 方法。
public class Test {
} @Component(modules = TestCreate.class) public interface TestCreateComponent {
Test ceshi(); } TestCreateComponent testCreateComponent = DaggerTestCreateComponent.create(); Test test = testCreateComponent.ceshi();
上面代码中创建 TestCreateComponent 并没有借助于 Builder,而是直接调用了 DaggerTestCreateComponent 的 create() 方法,但是它有一个前提,这个前提就是 Component 中的 module 中被 @Provides 注解的方法都必须是静态方法,也就是它们必须都被 static 修饰。
@Module public class TestCreate {
@Provides public static int provideTest1() { return 1; } @Provides public static String provideTest2() { return "test component create()"; } @Provides public static Test provideTest(){ return new Test(); } }
因为不需要创建 Module 对象实例,所以 Builder 自然就可以省去了。
@Inject 和 @Provides 的优先级
可能有心思细腻的同学会问,同样是提供依赖,如果一个类被 @Inject 注解了构造方法,又在某个 Module 中的 @Provides 注解的方法中提供了依赖,那么最终 Dagger2 采用的是哪一个?
public class Baozi {
String name; @Inject public Baozi() { name = "小笼包"; } public Baozi(String name) { this.name = name; } @Override public String toString() { return name; } } @Module public class ShangjiaAModule {
String restaurant; public ShangjiaAModule(String restaurant) { this.restaurant = restaurant; } @Provides public Baozi provideBaozi() { return new Baozi("豆沙包"); } }
Baozi 这个类就符合我上面给的情景,一方面它确实拥有被 @Inject 注解过的构造方法,另一方面在 Module 中它又通过 @Provides 提供了依赖。那么,最终,Dagger2 采取了哪一种呢?
答案是 Module,其实现象我们在之前的测试时已经可以观察到了,最终屏幕显示的是豆沙包选项。
Dagger2 依赖查找的顺序是先查找 Module 内所有的 @Provides 提供的依赖,如果查找不到再去查找 @Inject 提供的依赖。
到这里,我们讲解了 Dagger2 中最常见的 4 个注解:@Inject、@Component、@Module、@Provides。
正常情况下,这 4 个注解能够很好的完成一般的代码开发了。但是,这都是基础功能,Dagger2 提供了更多的一些特性。
Dagger2 中的单例 @Singleton
我们在平常开发中经常要涉及到各种单例。比如在 Android 中开发,数据库访问最好要设计一个单例,网络访问控制最好设计一个单例。我们经常编写这样的代码。
public class DBManager {
private static DBManager instance; private DBManager() { } public static DBManager getInstance() { if ( instance == null ) { synchronized ( DBManager.class ) { if ( instance == null ) { instance = new DBManager(); } } } return instance; } }
这种代码手段千遍一律,而 Dagger2 提供了另外一种可能。那就是利用 @Singleton 注解解决它。@Singleton 怎么使用呢?我们用代码来说明。
@Singleton public class TestSingleton {
@Inject public TestSingleton() { } } @Singleton @Component public interface ActivityComponent {
void inject(SecondActivity activity); }
用 @Singleton 标注在目标单例上,然后用 @Singleton 标注在 Component 对象上。
编写测试代码
public class SecondActivity extends AppCompatActivity {
@Inject TestSingleton testSingleton1; @Inject TestSingleton testSingleton2; Button mBtnTestSingleton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sencond); mBtnTestSingleton = (Button) findViewById(R.id.btn_test_singleton); DaggerActivityComponent.builder() .build() .inject(this); mBtnTestSingleton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(SecondActivity.this,"test1 hashcode:"+testSingleton1.toString() +" test2 hashcode:"+testSingleton2.toString(),Toast.LENGTH_LONG).show(); } }); } }
@Module public class SecondActivityModule {
@Provides @Singleton public TestSingleton provideTestSingleton(){ return new TestSingleton(); } }
@Singleton 引出 @Scope
我们在上一节的内容可以看到,通过 @Singleton 注解就可以实现一个单例了。本节的目标就是深入分析一下 @Singleton。
@Scope @Documented @Retention(RUNTIME) public @interface Singleton {
}
@Singleton 是一个注解,但是它被一个元注解 @Scope 注解了,所以,可以猜测到的是 @Scope 是真正厉害的角色。而实际上 @Singleton 只是 @Scope 一个默认的实现而已,但是因为它更具可读性,能够让开发者一眼就明白它的作用是为了单例。但是,单例也是有范围限制的。
分析 @Singleton 其实就等同于分析 @Scope 。Scope 的字面意思是作用域,也就是表达一种能力的范围。那么在 Dagger2 中它表达了一种什么样的能力范围呢?
大家有没有想过,为什么要用 @Singleton 同时标注 @Provides 和 @Component ?
文章一开始就讲过,Component 是联系需求与依赖的纽带,所以用 @Singleton 确定的单例作用域应该也是在 Component 的范围内。也就是说 @Scope 的作用范围其实就是单例能力范围,这个范围在单个的 Component 中。
在上面的代码中,MainActivity 和 SecondActivity 运用了不同的 Component 现在我们可以测试一下它们所获取的 TestSingleton 会不会是同一个对象。
public class MainActivity extends AppCompatActivity {
@Inject public TestSingleton testSingleton; onCreate() { mBtnJumpToSecond.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this,SecondActivity.class); startActivity(intent); Toast.makeText(MainActivity.this,"testsingleton is "+ testSingleton,Toast.LENGTH_LONG).show(); } }); } } @Module public class ActivityModule {
@Provides public int provideActivityTest(){ return ; } @Provides @Singleton public TestSingleton provideSingleton(){ return new TestSingleton(); } } @Singleton @Component(modules = {ShangjiaAModule.class,ActivityModule.class}) public interface WaimaiPingTai {
ZhaiNan waimai(); void zhuru(ZhaiNan zhaiNan); void inject(MainActivity mainActivity); }
@Singleton 起作用是因为它被 @Scope 注解,所以,如果可能,我们也可以自己定义 Scope。
@Scope @Documented @Retention(RUNTIME) public @interface PageScope {
}
我自己定义一个 @PageScope 注解,我的想法是一个 Activity 有不同的 Fragment,所以以 @PageScope 标注的依赖对象在这些 Fragment 之间是同一个对象,也就是说在这个 Activity 中实现了单例。而在另外一个 Activity 中因为采取了不同的 Component 对象,所以它们的 Fragment 也共用了同一个依赖对象,但是两个 Activity 中各自的依赖确不是同一个对象。
大家细细体会。
@Qualifiers 和 @Name
Qualifiers 是修饰符的意思,那么它修饰的是什么呢?不知道大家有没有察觉到,前面的演示代码其实很简单,经不起太多推敲。
在一个 Module 中 @Provides 提供的依赖是由返回值决定的。这样就会出现问题,同一种类型不同实例,怎么去区别?比如
public class SecondActivity extends AppCompatActivity {
@Inject String phone; @Inject String computer; }
phone 和 computer 应该要对应不同的字符串。但是,我们该如何在 Module 中进行编码呢?
@Module public class SecondActivityModule {
@Provides @Singleton public TestSingleton provideTestSingleton(){ return new TestSingleton(); } @Provides public String providePhone() { return "手机"; } @Provides public String providePhone() { return "电脑"; } }
大家可能会想到这样编码,但是这样的代码根本编译不过。因为 Dagger2 是根据返回的类型来进行依赖关系确定的。如果存在两个方法返回一样的类型,那么正常情况下 Dagger2 显然就没有办法处理了。
不过,Dagger2 给出了解决方案。用 @Name 注解就好了,配合 @Inject 和 @Provides 一起使用。例如
@Inject @Named("phone") String phone; @Inject @Named("computer") String computer; @Module public class SecondActivityModule {
@Provides @Singleton public TestSingleton provideTestSingleton(){ return new TestSingleton(); } @Provides @Named("phone") public String providePhone() { return "手机"; } @Provides @Named("computer") public String provideComputer() { return "电脑"; } }
当然,如果你嫌每次给 @Name 麻烦,你可以自定义注解。
@Qualifier @Documented @Retention(RUNTIME) public @interface Named {
/ The name. */ String value() default ""; }
@Name 只是被 @Qualifier 注解的一个注解。所以,它能够有效完全是因为 @Qualifier。
@Qualifier @Documented @Retention(RUNTIME) public @interface Phone {
} @Qualifier @Documented @Retention(RUNTIME) public @interface Computer {
}
通过 @Qualifier 建立了 @Phone 和 @Computer 注解。
@Inject @Phone String phone; @Inject @Computer String computer; @Module public class SecondActivityModule {
@Provides @Singleton public TestSingleton provideTestSingleton(){ return new TestSingleton(); } @Provides @Phone public String providePhone() { return "手机"; } @Provides @Computer public String provideComputer() { return "电脑"; } }
这样的效果是一样的。但是好处在于 @Name() 中要传入字符串,一不小心就容易将单词拼错,容易出错。
Dagger2 中的延迟加载
public class TestLazy {
String name; public String getName() { if ( name == null ) { name = "TestLazy"; } return name; } }
只有调用 TestLazy 实例的 getName() 方法时,name 才会被初始化。
Dagger2 提供了延迟加载能力。只需要通过 Lazy 就好了,Lazy 是泛型类,接受任何类型的参数。
public class TestLazy {
@Inject @Named("TestLazy") Lazy
name;
public String
getName() {
return name.get(); } }
@Module
public
class SecondActivityModule {
@Provides
@Singleton
public TestSingleton
provideTestSingleton(){
return
new TestSingleton(); }
@Provides
@Phone
public String
providePhone() {
return
"手机"; }
@Provides
@Computer
public String
provideComputer() {
return
"电脑"; }
@Provides
@Named(
"TestLazy")
public String
provideTestLazy() {
return
"TestLazy"; } }
这样,只有第一次调用 TestLazy 的 getName() 方法时,name 都会被注入。
Provider 强制重新加载
应用 @Singleton 的时候,我们希望每次都是获取同一个对象,但有的时候,我们希望每次都创建一个新的实例,这种情况显然与 @Singleton 完全相反。Dagger2 通过 Provider 就可以实现。它的使用方法和 Lazy 很类似。
public class TestProvider {
@Inject Provider
randomValue;
public
int
getRandomValue () {
return randomValue.get().intValue(); } }
@Module
public
class SecondActivityModule {
......
@Provides
public
int
provideRandomValue(){
return (
int) Math.random(); } }
但是,需要注意的是 Provider 所表达的重新加载是说每次重新执行 Module 相应的 @Provides 方法,如果这个方法本身每次返回同一个对象,那么每次调用 get() 的时候,对象也会是同一个。
Dagger2 中 Component 之间的依赖。
在程序开发中,可以存在多个 Component,而且 Component 之间还可以有依赖关系。比如
public class Guazi {
} public class Huotuichang {
} @Module public class XiaoChiModule {
@Provides public Guazi provideGuazi() { return new Guazi(); } @Provides public Huotuichang provideHuotuichang() { return new Huotuichang(); } } @Component(modules = XiaoChiModule.class) public interface XiaoChiComponent {
Guazi provideGuazi(); Huotuichang provideHuotuichang(); }
XiaoChiComponent 这个 Component 主要作用是提供瓜子和火腿肠这些依赖。
现在,我要新建立一个 Component 代表食物类,并且食物类包括小吃,因此,我们得想办法利用 XiaoChiCompoent 这个现成的 Component。
@Module public class FoodModule {
@Provides public Baozi provideBaozi() { return new Baozi(); } @Provides public Noodle provideNoodle() { return new Kangshifu(); } } @Component(modules = XiaoChiModule.class ,dependencies = XiaoChiComponent.class) public interface FoodComponent {
void inject(ThirdActivity activity); }
只需要在 @Component 中的 dependencies 属性取值为相应的依赖就可以了。这样,FoodComponent 也提供了瓜子和火腿肠的依赖。我们再看如何使用?
public class ThirdActivity extends AppCompatActivity {
Button mBtnTest; @Inject Guazi guazi; @Inject Huotuichang huotuichang; @Inject Baozi baozi; @Inject Noodle noodle; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); mBtnTest = (Button) findViewById(R.id.test_dependency); XiaoChiComponent xiaoChiComponent = DaggerXiaoChiComponent.builder() .build(); DaggerFoodComponent.builder() .xiaoChiComponent(xiaoChiComponent) .build() .inject(this); mBtnTest.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(ThirdActivity.this, baozi+" "+ noodle+" " +guazi+""+huotuichang,Toast.LENGTH_LONG).show(); } }); } }
只需要在 DaggerFoodComponent 构建的时候,将所依赖的 Component 传递进去就是了,但前提是要先创建 XiaochiComponent ,然后创建 FoodComponent 的时候将它传递进去。如上面调用了 DaggerFoodComponent.Builder 的 xiaochiComponent() 方法。
Dagger2 中的 SubComponent
@Subcomponent(modules = FoodModule.class) public interface SubComponent {
void inject(ThirdActivity activity); } @Component(modules = XiaoChiModule.class) public interface ParentComponent {
SubComponent provideSubComponent(); } DaggerParentComponent.builder().build() .provideSubComponent().inject(this);
使用 Subcomponent 时,还是要先构造 ParentComponent 对象,然后通过它提供的 SubComponent 再去进行依赖注入。
大家可以细细观察下它与 depedency 方法的不同之处。
但是,SubComponent 同时具备了 ParentComponent 和自身的 @Scope 作用域。所以,这经常会造成混乱的地方。大家需要注意。
如果你要我比较,SubComponent 和 dependency 形式哪种更好时,我承认各有优点,但我自己倾向于 dependency,因为它更灵活。
不是说 组合优于继承嘛。
到这里的时候,Dagger2 的基础知识都介绍的差不多了,它还有一些知识点,但是应用的场景太复杂,所以没有必要细究。有兴趣的同学可以到官网上自行研究。
Dagger2 在什么地方进入依赖注入?如何注入?
也许会有一部分同学,执着于细节。因为 Dagger2 帮我们进行了依赖注入,但这一切过程是透明的,我们并不知晓。有探索精神的同学总想去获取更多的细节,这种精神值得称赞。
我简单说一下,Dagger2 运用了 APT 插件,这种插件会在编译时根据 @Provide、@Inject、@Moudle、@Component 这些注解生成许多中间代码,但是不管它多么复杂它的目的也只是为了依赖注入。所以,肯定有一个地方进行了。
a.setB(b);
这样的操作。
前面说过 Component 是需求与依赖的联系,因此可以在 Component 的实现类代码中找出分别代表需求、注射者、依赖 3 个角色,然后找出依赖注入发生时的代码,这个问题就算解答完成了。
@Component(modules = SecondActivityModule.class) public interface ActivityComponent {
void inject(SecondActivity activity); }
我们以 ActivityComponent 为例解释说明,在这里显然 SecondActivity 的实例是需求者。
@Inject TestSingleton testSingleton1; @Inject TestSingleton testSingleton2; @Inject @Phone String phone; @Inject @Computer String computer;
SecondActivity 它需要 2 种依赖,TestSingleton、String。我们去查看最关键的类,也就是 Dagger2 帮助我们生成的 DaggerActivityComponent。
public final class DaggerActivityComponent implements ActivityComponent {
private Provider
provideTestSingletonProvider;
private Provider
providePhoneProvider;
private Provider
provideComputerProvider;
private MembersInjector
secondActivityMembersInjector;
private
DaggerActivityComponent(Builder builder) {
assert builder !=
null; initialize(builder); } }
我在前面讲解过 Provider 是什么用处,大家一看就懂。而 MembersInjector 以 Injector 为后缀,所以它肯定是一个注射器。我根本不需要去查看它的定义和相关源码。
好了,现在注射者(MembersInjector)找到了,依赖(Provider provideTestSingletonProvider 等等)找到了,需求者我们知道是 SecondActivity,所以我们只要把依赖注入发生的代码找出来,问题就解答完成。
@Override public void inject(SecondActivity activity) { secondActivityMembersInjector.injectMembers(activity); }
inject() 方法显然是依赖注入发生的地方,但它内部调用了 secondActivityMembersInjector.injectMembers() 方法,我们跟踪进去。
public void injectMembers(SecondActivity instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.testSingleton1 = testSingleton1AndTestSingleton2Provider.get(); instance.testSingleton2 = testSingleton1AndTestSingleton2Provider.get(); instance.phone = phoneProvider.get(); instance.computer = computerProvider.get(); }
代码交待的一清二楚,SecondActivity 实例 instance 的 testSingleton1、testSingleton2、phone、computer 4 个依赖,全部在这里进行赋值,也就是在这里进行依赖注入。
不过,我们都知道依赖都是由 Module 提供的,回到 DaggerActivityComponent 的源码当中关注它的 initialize 方法。
private void initialize(final Builder builder) { this.provideTestSingletonProvider = DoubleCheck.provider( SecondActivityModule_ProvideTestSingletonFactory.create(builder.secondActivityModule)); this.providePhoneProvider = SecondActivityModule_ProvidePhoneFactory.create(builder.secondActivityModule); this.provideComputerProvider = SecondActivityModule_ProvideComputerFactory.create(builder.secondActivityModule); this.secondActivityMembersInjector = SecondActivity_MembersInjector.create( provideTestSingletonProvider, providePhoneProvider, provideComputerProvider); }
它们都是通过工厂方法创建的。大家应该都懂工厂方法都是用来创建对象的。我不嫌麻烦,挑出 SecondActivityModule_ProvidePhoneFactory 这个个例来进行讲解。它代表 Phone 对象的工厂。
public final class SecondActivityModule_ProvidePhoneFactory implements Factory<String> {
private final SecondActivityModule module; public SecondActivityModule_ProvidePhoneFactory(SecondActivityModule module) { assert module != null; this.module = module; } @Override public String get() { return Preconditions.checkNotNull( module.providePhone(), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory
create(SecondActivityModule module) {
return
new SecondActivityModule_ProvidePhoneFactory(module); } }
public
interface Factory<T> extends Provider<T> {
}
public String get() { return Preconditions.checkNotNull( module.providePhone(), "Cannot return null from a non-@Nullable @Provides method"); }
所以最终会调用
@Module public class SecondActivityModule {
@Provides @Phone public String providePhone() { return "手机"; } }
其它几个流程类似,那就不一一分析了。有兴趣的同学可以去查看源码,我只提示一个信息,你去观察 MembersInjector 怎么创建 @Singleton 注解的依赖时会发现它的实现步骤,跟我前面文章手动生成 DBManager 单例用的手段是很相似的。
对于 Dagger2 的用途与意义心生迷惑
这个项目示例就是为了演示 Dagger2 与 MVP 架构的配合使用。由于文章篇幅所限,我不作过多的讲解,大家自行研究。有机会,我会专门写一篇文章来讲述 Dagger2 在一个完整项目工程中如何进行解耦的。
如果你对 Dagger2 兴趣更浓烈了
Dagger2 的知识内容稍多,所以如果你耐着性子学习完后,兴致依赖不减,我可以给你一些建议。
- 自己去阅读官网文档。因为那才是第一手资料,虽然它写的不是很好,但毕竟权威。
- 多去观察不同的博文,因为每个人思考方式不一样,所以观察问题的角度可能不一样。
- 自己多练,任何没有经过自己实践的行为在软件编程中都不可取。
- 阅读优秀的开源代码,并思考。
- 在思考的同时,纠正自己的理解,然后再实践,再思考,再总结。我总说主动学习要好过被动学习,任何没有经过自己思考和求索的学习都是被动学习,看文档、看博文、看代码那都是别人的知识,你需要的就是用自己把这些纳入自己的知识体系中,当你也能讲述给其他人听的时候,那时你就可以确定你掌握它了。
如果你仍然意识不到 Dagger2 的美好
这个其实也没有多大关系。不要迷恋武器。
也许你写的代码中类文件不是很多,模块之间的耦合并不是很强,或者是整个系统并不复杂,强行引进 Dagger2 只会让你感受复杂,多了很多类,多了很多编译的步骤,还增加了学习成本,你会觉得不划算。
我们总说优化代码,设计架构,其实对于很多开发人员而言,大量的产品需求就能让自己加好几个晚上的班,并且需求还经常变动,如果你这个时候跟他讲代码规范什么的,肯定不现实。现实的事情是,完成需求永远第一要务。
饱暖才能思淫欲。
我与其劝你去感受 Dagger2 的美好,还不如劝你细细去体会一下依赖注入的美好。
如果,你仍然觉得 Dagger2 麻烦,中肯地讲一句:那么索性放弃它算了,Dagger2 不重要,依赖注入才重要。
等到哪一天,你真的有强烈的对于代码解耦需求,也许你会想起 Dagger2 这么一款框架,那时候回过来学习,我保证你的效果会非常明显,你的理解力也会较现在更加的深刻。
所以,我最终的目的仍然是希望你能够好好学习 Dagger2,希望大家不要误解我这一节的真实用意。
demo

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/205896.html原文链接:https://javaforall.net
