Android Dagger2依赖注入
—— 所谓的光辉岁月,并不是以后,闪耀的日子,而是无人问津时,你对梦想的偏执。
Dagger2起源于Dagger,是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。
起初Square公司受到Guice的启发而开发了Dagger,但是Dagger这种半静态半运行时的框架还是有些性能问题(虽说依赖注入是完全静态的,但是其有向无环图(Directed Acyclic Graph)还是基于反射来生成的,这无论在大型的服务端应用还是在Android应用上都不是最优方案)。因此Google工程师Fork了Dagger项目,对它进行了改造。于是变演变出了今天我们要讨论的Dagger2,所以说Dagger2其实就是高配版的Dagger。
依赖注入(Dependency Injection)
那么什么是依赖注入呢?在解释这个概念前我们先看一小段代码:
public class Car{ private Engine engine; public Car(){ engine = new Engine(); } }
这段Java代码中Car类持有了对Engine实例的引用,我们称之为Car类对Engine类有一个依赖。而依赖注入则是指通过注入的方式实现类与类之间的依赖,下面是常见的三种依赖注入的方式:
1、构造注入:通过构造函数传参给依赖的成员变量赋值,从而实现注入。
public class Car{ private Engine engine; public Car(Engine engine){ this.engine = engine; } }
2、接口注入:实现接口方法,同样以传参的方式实现注入。
public interface Injection{ void inject(T t); } public class Car implements Injection { private Engine engine; public Car(){} public void inject(Engine engine){ this.engine = engine; } }
3、注解注入:使用Java注解在编译阶段生成代码实现注入或者是在运行阶段通过反射实现注入。
public class Car{ @Inject Engine engine; public Car(){} }
前两种注入方式需要我们编写大量的模板代码,而机智的Dagger2则是通过Java注解在编译期来实现依赖注入的。
为什么需要依赖注入
我们之所是要依赖注入,最重要的就是为了解耦,达到高内聚低耦合的目的,保证代码的健壮性、灵活性和可维护性。
下面我们看看同一个业务的两种实现方案:
1、方案A
public class Car{ private Engine engine; private List<Wheel> wheels; public Car(){ engine = new Engine(); wheels = new ArrayList<>(); for(int i = 0; i < 4; i++){ wheels.add(new Wheel()); } } public void start{ System.out.println("启动汽车"); } } public class CarTest{ public static void main(String[] args){ Car car = new Car(); car.start(); } }
2、方案B
public class Car{ private Engine engine; private List<Wheel> wheels; public Car(Engine engine, List<Wheel> wheels){ this.engine = engine; this.wheels = wheels; } public void start{ System.out.println("启动汽车"); } } public class CarTest{ public static void main(String[] args){ Engine engine = new Engine(); List<Wheel> wheels = new ArrayList<>(); for(int i = 0; i < 4; i++){ wheels.add(new Wheel()); } Car car = new Car(engine, wheels); car.start(); } }
方案A:由于没有依赖注入,因此需要我们自己是在Car的构造函数中创建Engine和Wheel对象。
方案B:我们手动以构造函数的方式注入依赖,将engine和wheels作为参数传入而不是在Car的构造函数中去显示的创建。
方案A明显丧失了灵活性,一切依赖都是在Car类的内部创建,Car与Engine和Wheel严重耦合。一旦Engine或者Wheel的创建方式发生了改变,我们就必须要去修改Car类的构造函数(比如说现在创建Wheel实例的构造函数改变了,需要传入Rubber(橡胶)了);另外我们也没办法替换动态的替换依赖实例(比如我们想把Car的Wheel(轮胎)从邓禄普(轮胎品牌)换成米其林(轮胎品牌)的)。这类问题在大型的商业项目中则更加严重,往往A依赖B、B依赖C、C依赖D、D依赖E;一旦稍有改动便牵一发而动全身,想想都可怕!而依赖注入则很好的帮我们解决了这一问题。
Dagger2注解
开篇我们就提到Dagger2是基于Java注解来实现依赖注入的,那么在正式使用之前我们需要先了解下Dagger2中的注解。Dagger2使用过程中我们通常接触到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。
@Inject:@Inject有两个作用,一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖;二是用来标记构造函数,Dagger2通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject标记了的变量提供依赖;
@Module:@Module用于标注提供依赖的类。你可能会有点困惑,上面不是提到用@Inject标记构造函数就可以提供依赖了么,为什么还需要@Module?很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject注解,又比如说提供以来的构造函数是带参数的,如果我们之所简单的使用@Inject标记它,那么他的参数又怎么来呢?@Module正是帮我们解决这些问题的。
@Provides:@Provides用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值;
@Component:@Component用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被Component标注的接口在编译时会生成该接口的实现类(如果@Component标注的接口为CarComponent,则编译期生成的实现类为DaggerCarComponent),我们通过调用这个实现类的方法完成注入;
@Qulifier:@Qulifier用于自定义注解,也就是说@Qulifier就如同Java提供的几种基本元注解一样用来标记注解类。我们在使用@Module来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以provide开头,但这并不是强制的,只是为了增加可读性而已)。那么Dagger2怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2根据返回值的类型来决定为哪个被@Inject标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型Dagger2就懵逼了。@Qulifier的存在正式为了解决这个问题,我们使用@Qulifier来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject标注的变量),这样Dagger2就知道为谁提供依赖了。----一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;
@Scope:@Scope同样用于自定义注解,我能可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;
@Singleton:@Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的Component是否为一个全局对象。
------转载请注明出处,感谢您对原创作者的支持------
有偿提供技术支持、Bug修复、项目外包、毕业设计、大小作业
Android学习小站
Q Q:1095817610
微信:jx-helu
邮箱:1095817610@qq.com
添加请备注"Android学习小站"
