依赖注入框架Dagger2详解(一),依赖注入和控制反转的深入理解
依赖注入框架Dagger2详解(二),Java依赖注入标准JSR-330规范
依赖注入框架Dagger2详解(三),Java注解处理器APT入门
依赖注入框架Dagger2详解(四),初级篇
依赖注入框架Dagger2详解(五),中级篇
依赖注入框架Dagger2详解(六),高级篇
什么是Dagger2
Dagger2是一种依赖注入的框架,能够在编译时自动生成出一些代码,这些代码可以帮助对应的实例初始化。 ,它是鼎鼎大名的Square公司旗下又一把利刃,还有一把黄油刀,叫做ButterKnife。
Dagger2起源于Dagger,是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。
起初Square公司受到Guice的启发而开发了Dagger,但是Dagger这种半静态半运行时的框架还是有些性能问题,还是基于反射来生成的,这无论在大型的服务端应用还是在Android应用上都不是最优方案。因此Google工程师Fork了Dagger项目,对它进行了改造。于是变演变出了今天我们要讨论的Dagger2,所以说Dagger2其实就是高配版的Dagger。
依赖注入是面向对象编程的一种设计模式,其目的是为了降低程序耦合,这个耦合就是类之间的依赖引起的。
举个例子:我们在写面向对象程序时,往往会用到组合,即在一个类中引用另一个类,从而可以调用引用的类的方法完成某些功能,就像下面这样
public class ClassA {
...
ClassB b;
...
public ClassA() {
b = new ClassB();
}
public void do() {
...
b.doSomething();
...
}
}
这个时候就产生了依赖问题,ClassA依赖于ClassB,必须借助ClassB的方法,才能完成一些功能。这样看好像并没有什么问题,但是我们在ClassA的构造方法里面直接创建了ClassB的实例,问题就出现在这,在ClassA里直接创建ClassB实例,违背了单一职责原则,ClassB实例的创建不应由ClassA来完成;其次耦合度增加,扩展性差,如果我们想在实例化ClassB的时候传入参数,那么不得不改动ClassA的构造方法,不符合开闭原则。
因此我们需要一种注入方式,将依赖注入到宿主类(或者叫目标类)中,从而解决上面所述的问题。依赖注入有一下几种方式:
- 通过接口注入
interface ClassBInterface {
void setB(ClassB b);
}
public class ClassA implements ClassBInterface {
ClassB classB;
@override
void setB(ClassB b) {
classB = b;
}
}
- 通过set方法注入
public class ClassA {
ClassB classB;
public void setClassB(ClassB b) {
classB = b;
}
}
- 通过构造方法注入
public class ClassA {
ClassB classB;
public void ClassA(ClassB b) {
classB = b;
}
}
- 通过Java注解
public class ClassA {
//此时并不会完成注入,还需要依赖注入框架的支持,如RoboGuice,Dagger2
@inject ClassB classB;
...
public ClassA() {}
}
在Dagger2中用的就是最后一种注入方式,通过注解的方式,将依赖注入到宿主类中,这样,ClassA的成员变量就自动初始化classB
我们再看另外一个例子,一个容器里面装的是苹果,不用Dagger2的情况下我们应该这么写:
public class Container{
Fruit f = new Apple(color,size);
...
}
上面例子面临着一个问题,Container依赖了Apple实现,如果某一天需要修改Apple为Banana,那么你一定得改Container的代码。有没有一种方法可以不改Container呢?
可以使用Dagger2,我们可以把代码改成
public class Container{
@Inject
Fruit f;
...
}
这样,Container的成员变量就自动初始化成Apple实例了,Container不用关心具体用哪个Fruit的实现,也不用关心到底用什么颜色多大的苹果。假如某一天要把苹果替换成香蕉,Container的代码是完全不需要改动的。从某种意义上说,Dagger2就是一个帮你写工厂代码的工具,当然Dagger2的功能比工厂模式更加强大。
Dagger2 的三要素
Dagger2要实现一个完整的依赖注入,必不可少的元素有三种,Module,Component,Container。- Container就是可以被注入的容器,具体对应上文例子中的Container,Container拥有需要被初始化的元素。需要被初始化的元素必须标上@Inject,只有被标上@Inject的元素才会被自动初始化。@Inject在Dagger2中一般标记构造方法与成员变量。
- Module 可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从Module生产的。
- 有了可以被注入的容器Container,也有了提供依赖对象的Module。我们必须将依赖对象注入到容器中,这个过程由Component来执行。Component将Module中产生的依赖对象自动注入到Container中。
如何引入Dagger2
- 配置apt插件(在build.gradle(Project:xxx)中添加如下代码)
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
//添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
- 添加依赖(在build.gradle(Module:app)中添加如下代码)
apply plugin: 'com.android.application'
//添加如下代码,应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
//java注解
compile 'org.glassfish:javax.annotation:10.0-b28'
...
}
基本用法
实现Module
Module其实就是一个依赖的制造工厂。我们只需要为其添加制造依赖的方法即可。
@Module //1 注明本类属于Module
public class FruitModule{
@Provides //2 注明该方法是用来提供依赖对象的特殊方法
public Fruit provideFruit(){
return new Apple(Color.RED,Size.BIG);
}
}
(1)中添加注解@Module注明本类属于Module
(2)中添加注解@Provides注明该方法是用来提供依赖对象的特殊方法
一个完整的Module必须拥有@Module与@Provides注解
实现Component
Component就是一个将Module生成的实例注入Container中的注入器。我们来写一个水果注入器:
@Component(modules={FruitModule.class}) //3 指明Component在哪些Module中查找依赖
public interface FruitComponent{ //4 接口,自动生成实现
void inject(Container container); //5 注入方法,在Container中调用
}
(3)中@Component使用modules指向使用的Module的集合。
(4)所有的Component都必须以接口形式定义。Dagger2框架将自动生成Component的实现类,对应的类名是Dagger×××××,这个例子中对应的实现类是DaggerFruitComponent
(5)中添加注入方法,一般使用inject做为方法名,方法参数为对应的Container
实现Container
Container就是可以被注入依赖关系的容器。具体实现如下
public Container{
@Inject //6 添加@Inject,标记f可以被注入
Fruit f;
public void init(){
DaggerFruitComponent.create().inject(this); //7 使用FruitComponent的实现类注入
}
}
Container除了代码中(6)标记f需要被注入外,还需要代码中(7)调用Component的实现类将Module的生成的对象注入到f中。
到此,当调用Container的init()方法时,Contianer中的f将会自动初始化成实现类Apple的对象。 以后如果想更改Fruit的实现类,只需要在@Component中的modules指向不同的Module即可。而Container的代码完全不需要改动。因为Container已经不再依赖Apple实现了。
这就是Dagger2的基本用法,下一篇我们将学习它的更高级一些的用法。