依赖注入框架Dagger2详解(四),初级篇

依赖注入框架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。
Dagger2架构图
  • 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的基本用法,下一篇我们将学习它的更高级一些的用法。

你可能感兴趣的:(依赖注入框架Dagger2详解(四),初级篇)