Dagger2 使用详解(1)--依赖注入

2017/1/9 posted in  Dagger2  

  Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(没错!还有一把黄油刀,唤作ButterKnife);故此给本篇取名神兵利器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<T>{

    void inject(T t);
}

public class Car implements Injection<Engine>{

    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;一旦稍有改动便牵一发而动全身,想想都可怕!而依赖注入则很好的帮我们解决了这一问题。