依赖注入简介
在介绍Dagger框架之前我们先来看看依赖注入(Dependence Injection),依赖注入还有另一个名字叫控制反转(Inversion of Control,英文缩写为IoC),当你看到这两个名词时他们大多指的都是同一个概念(实际上依赖注入是控制反转的一部分,其另一部分称为依赖查找(Dependenc Lookup)使用相对较少且不在本文涉及范畴),那什么是依赖注入呢?依赖了什么?又注入了什么?
不懂的东西我们自然要去问谷哥和度娘了,我搜到了一篇文章讲的还满详细的,大家可以去学习一下:依赖注入那些事儿,这篇文章虽然是基于c#语言写的但是用在JAVA上也是丝毫没有问题的。
在默认大家已经读了上面那个链接里的文章N遍以后,我来用简单的语言总结一下通常的代码结构中,调用者会直接决定实现的方式,比如调用者需要一本书的实例,在初始化的时候就会决定这本书是语文书还是数学书。而在依赖注入的世界里调用者只会声明我需要一本书,而在执行的时候才会根据外部条件决定给调用者语文书还是数学书。
无论你是否理解了依赖注入,我们先来看下依赖注入的优缺点(这些点可能部分和Dagger框架会不一致,仅供增进大家对依赖注入的理解):
优点:
- 代码分层:把逻辑分层后每层可以独立的编写和进行单元测试,而不用担心影响到其他层的功能(只要不改变接口的实现)
- 动态配置:通过XML等方式以及反射机制可以在运行时改变程序的行为而不必重新编译代码
- 让框架看起来更高端(这个是随便BB的啦)
不足:
- 生成一个对象的步骤变复杂了(事实上操作上还是挺简单的),对于不习惯这种方式的人,会觉得有些别扭和不直观。
- 对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。
- 缺少IDE重构操作的支持,如果在Eclipse要对类改名,那么你还需要去XML文件里手工去改了,这似乎是所有XML方式的缺憾所在。
Dagger2框架简介及实用
以下部分内容引用自详解Dagger2
Dagger1简介
有Dagger2自然就有Dagger1,先简单说下Dagger1。Dagger1框架是由Square公司受到Guice启发创建的,也是Android上最常用的依赖注入框架
Dagger1基本特点:
- 多个注入点:依赖,通过injected
- 多种绑定方法:依赖,通过provided
- 多个modules:实现某种功能的绑定集合
- 多个对象图: 实现一个范围的modules集合
Dagger1是在编译的时候实行绑定,不过也用到了反射机制。但这个反射不是用来实例化对象的,而是用于图的构成。Dagger会在运行的时候去检测是否一切都正常工作,所以使用的时候会付出一些代价:偶尔会无效和调试困难。
详细的细节我们就不多说了,这不是我们关注的重点。
Dagger2简介
Dagger2是Dagger1的分支,由谷歌公司接手开发,目前的版本是2.0。Dagger2是受到AutoValue项目的启发。 刚开始,Dagger2解决问题的基本思想是:利用生成和写的代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。
如果我们将Dagger2和1比较,他们两个在很多方面都非常相似,但也有很重要的区别,如下:
- 再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
- 容易调试和可跟踪:完全具体地调用提供和创建的堆栈
- 更好的性能:谷歌声称他们提高了13%的处理性能
- 代码混淆:使用派遣方法,就如同自己写的代码一样
当然所有这些很棒的特点都需要付出一个代价,那就是缺乏灵活性,例如:Dagger2没用反射所以没有动态机制。
Dagger2简单实用手册
Dagger2的详细官方文档可以在这里找到:http://google.github.io/dagger/,本章基本源于对官方文档的翻译,加上了部分自己的想法帮助大家更快理解框架内容
整个Dagger2框架是依赖于Java语言的注解(Annotation)系统实现的,在分析Espresso源码的过程中我们会遇到如下的注解:
- @Inject
- @Component
- @Singleton
- @Module
- @Provides
- @Subcomponent
@Inject注解
@Inject用于声明依赖,在类的构造函数上使用@Inject注解,声明该类可以被Dagger2实例化,同时如果该构造函数是带参数的,那么就会去使用使用了@Inject声明的构造函数生成一个实例传递进来。如示例代码中heater就会调用new Heater()方法生成一个Heater实例注入进来(Heater()方法已有@Inject注解)。特别要提到的是没有@Inject方法注解的类是无法被Dagger2框架实例化的:
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
也可以直接对成员变量注入
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
...
}
@Provides注解满足依赖
上文说到@Inject需要的依赖会自动调用构造方法,在有些情况下@Inject注解是无法生效的:
- Interface是无法实例化的
- 第三方库是无法添加注解的
- 需要配置的对象在给定配置前的实例化是没有意义的
在这种情况下我们需要@Provides注解来满足依赖,带有这个注解的函数的返回类型就能满足该类型所需的实例化依赖。举个例子,前面的Heater()方法并没有提供注入接口,那么当我们需要一个Heater对象时,如下的方法也能够返回一个Heater对象:
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides注解的方法也可以有自己的依赖,比如providePump方法就需要一个Thermosiphon类型的依赖:
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
@Module注解依赖图容器
@Provides不是随处都能使用的,是需要在@Module注解标记的类内部才能生效的
@Module
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
}
@Component注解依赖图的根节点
前面我们提到Dagger2框架会自动生成代码来根据依赖图产生类的实例,比如在示例代码中:
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
@Component的modules参数声明了依赖Module,同时声明一个接口及接口方法,Dagger2框架会自动生成一个以Dagger为前缀的类继承实现这个接口,在本例中生成的类会命名为DaggerCoffeeShop,并实现maker()方法,实现的方式是一个CoffeeMaker类型的依赖,即调用者可以使用DaggerCoffeeShop().maker()方法来获得这个依赖。当然这个书写是有点问题的,为了让Dagger2框架帮我自动生成一个目标类的实例,需要调用create()方法,所以它的实现应该是这样的:
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker();
}
}
@Subcomponent子Component注解
在@Component注解的接口类的方法中,除了可以返回注入项(实现了@Inject注解的构造方法的类,可以被Dagger2框架实例化)类别以外,还可以返回@Subcomponent注解的子Component类,以实现多层调用的效果,具体请看Espresso源码分析部分
@Singleton单例注解
这个注解是最好理解的,声明了这个注解的实例在生成实例时都是单例化的,如果之前以及生成过改类型的实例会直接返回之前生成的实例而不是新建一个该类的新的对象