「神兵利器Dagger2 | 掘金技术征文 」

Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(没错!还有一把黄油刀,唤做ButterKnife);故此给本篇取名神兵利器Dagger2。java

Dagger2起源于Dagger,是一款基于Java注解来实现的彻底在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提升代码的健壮性和可维护性。Dagger2在编译阶段经过apt利用Java注解自动生成Java代码,而后结合手写的代码来自动帮咱们完成依赖注入的工做。git

起初Square公司受到Guice的启发而开发了Dagger,可是Dagger这种半静态半运行时的框架仍是有些性能问题(虽然说依赖注入是彻底静态的,可是其有向无环图(Directed Acyclic Graph)仍是基于反射来生成的,这不管在大型的服务端应用仍是在Android应用上都不是最优方案)。所以Google工程师Fork了Dagger项目,对它进行了改造。因而变演变出了今天咱们要讨论的Dagger2,因此说Dagger2其实就是高配版的Dagger。github

依赖注入(Dependency Injection)

那么什么是依赖注入呢?在解释这个概念前咱们先看一小段代码:架构

public class Car{

    private Engine engine;

    public Car(){
        engine = new Engine();
    }
}复制代码

这段Java代码中Car类持有了对Engine实例的引用,咱们称之为Car类对Engine类有一个依赖。而依赖注入则是指经过注入的方式实现类与类之间的依赖,下面是常见的三种依赖注入的方式:框架

一、构造注入:经过构造函数传参给依赖的成员变量赋值,从而实现注入。

public class Car{

    private Engine engine;

    public Car(Engine engine){
        this.engine = engine;
    }
}复制代码

二、接口注入:实现接口方法,一样以传参的方式实现注入。

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;
    }

}复制代码

三、注解注入:使用Java注解在编译阶段生成代码实现注入或者是在运行阶段经过反射实现注入。

public class Car{

    @Inject
    Engine engine;

    public Car(){}
}复制代码

前两种注入方式须要咱们编写大量的模板代码,而机智的Dagger2则是经过Java注解在编译期来实现依赖注入的。ide

为何须要依赖注入

咱们之所是要依赖注入,最重要的就是为了解耦,达到高内聚低耦合的目的,保证代码的健壮性、灵活性和可维护性。函数

下面咱们看看同一个业务的两种实现方案:post

一、方案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();
    }
}复制代码

二、方案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呢?咱们先从Spring中的控制反转(IOC)提及。

谈起依赖注入,作过J2EE开发的同窗必定会想起Spring IOC,那经过迷之XML来配置依赖的方式真的很让人讨厌;并且XML与Java代码分离也致使代码链难以追踪。以后更加先进的Guice(Android端也有个RoboGuice)出现了,咱们再也不须要经过XML来配置依赖,但其运行时实现注入的方式让咱们在追踪和定位错误的时候却又万分痛苦。开篇提到过Dagger就是受Guice的启发而开发出来的;Dagger继承了前辈的思想,在性能又碾压了它的前辈Guice,可谓是长江后浪推前浪,前浪死在沙滩上。

又如开篇我在简介中说到的,Dagger是一种半静态半运行时的DI框架,虽然说依赖注入是彻底静态的,可是生成有向无环图(DAG)仍是基于反射来实现,这不管在大型的服务端应用仍是在Android应用上都不是最优方案。升级版的Dagger2解决了这一问题,从半静态变为彻底静态,从Map式的API变成申明式API(@Module),生成的代码更优雅高效;并且一旦出错咱们在编译期间就能发现。因此Dagger2对开发者的更加友好了,固然Dagger2也所以丧失了一些灵活性,但整体来讲利仍是远远大于弊的。

前面提到这种A B C D E连续依赖的问题,一旦E的建立方式发生了改变就会引起连锁反应,可能会致使A B C D都须要作针对性的修改;可是骚年,你觉得为这仅仅是工做量的问题吗?更可怕的是咱们建立A时须要按顺序先建立E D C B四个对象,并且必须保证顺序上是正确的。Dagger2就很好的解决了这一问题(不仅是Dagger2,在其余DI框架中开发者一样不须要关注这些问题)。

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是否为一个全局对象。

咱们提到@Inject和@Module均可以提供依赖,那若是咱们即在构造函数上经过标记@Inject提供依赖,有经过@Module提供依赖Dagger2会如何选择呢?具体规则以下:

  • 步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。
  • 步骤2:若存在提供依赖的方法,查看该方法是否存在参数。
    • a:若存在参数,则按从步骤1开始依次初始化每一个参数;
    • b:若不存在,则直接初始化该类实例,完成一次依赖注入。
  • 步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。
    • a:若存在参数,则从步骤1开始依次初始化每个参数
    • b:若不存在,则直接初始化该类实例,完成一次依赖注入。

Dagger2使用入门

前面长篇大论的基本都在介绍概念,下面咱们看看Dagger2的基本应用。关于Dagger2的依赖配置就不在这里占用篇幅去描述了,你们能够到它的github主页下去查看官方教程github.com/google/dagg…。接下来咱们仍是拿前面的Car和Engine来举例。

一、案例A

Car类是需求依赖方,依赖了Engine类;所以咱们须要在类变量Engine上添加@Inject来告诉Dagger2来为本身提供依赖。

public class Car {

    @Inject
    Engine engine;

    public Car() {
        DaggerCarComponent.builder().build().inject(this);
    }

    public Engine getEngine() {
        return this.engine;
    }
}复制代码

Engine类是依赖提供方,所以咱们须要在它的构造函数上添加@Inject

public class Engine {

    @Inject
    Engine(){}

    public void run(){
        System.out.println("引擎转起来了~~~");
    }
}复制代码

接下来咱们须要建立一个用@Component标注的接口CarComponent,这个CarComponent其实就是一个注入器,这里用来将Engine注入到Car中。

@Component
public interface CarComponent {
    void inject(Car car);
}复制代码

完成这些以后咱们须要Build下项目,让Dagger2帮咱们生成相关的Java类。接着咱们就能够在Car的构造函数中调用Dagger2生成的DaggerCarComponent来实现注入(这其实在前面Car类的代码中已经有了体现)

public Car() {
    DaggerCarComponent.builder().build().inject(this);
}复制代码

二、案例B

若是建立Engine的构造函数是带参数的呢?好比说制造一台引擎是须要齿轮(Gear)的。或者Eggine类是咱们没法修改的呢?这时候就须要@Module和@Provide上场了。

一样咱们须要在Car类的成员变量Engine上加上@Inject表示本身须要Dagger2为本身提供依赖;Engine类的构造函数上的@Inject也须要去掉,应为如今不须要经过构造函数上的@Inject来提供依赖了。

public class Car {

    @Inject
    Engine engine;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngine() {
        return this.engine;
    }
}复制代码

接着咱们须要一个Module类来生成依赖对象。前面介绍的@Module就是用来标准这个类的,而@Provide则是用来标注具体提供依赖对象的方法(这里有个不成文的规定,被@Provide标注的方法命名咱们通常以provide开头,这并非强制的但有益于提高代码的可读性)。

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    @Provides Engine provideEngine(){
        return new Engine("gear");
    }
}复制代码

接下来咱们还须要对CarComponent进行一点点修改,以前的@Component注解是不带参数的,如今咱们须要加上modules = {MarkCarModule.class},用来告诉Dagger2提供依赖的是MarkCarModule这个类。

@Component(modules = {MarkCarModule.class})
public interface CarComponent {
    void inject(Car car);
}复制代码

Car类的构造函数咱们也须要修改,相比以前多了个markCarModule(new MarkCarModule())方法,这就至关于告诉了注入器DaggerCarComponentMarkCarModule提供的依赖注入到了Car类中。

public Car() {
   DaggerCarComponent.builder()
           .markCarModule(new MarkCarModule())
           .build().inject(this);
}复制代码

这样一个最最基本的依赖注入就完成了,接下来咱们测试下咱们的代码。

public static void main(String[] args){
    Car car = new Car();
    car.getEngine().run();
}复制代码

输出

引擎转起来了~~~复制代码

三、案例C

那么若是一台汽车有两个引擎(也就是说Car类中有两个Engine变量)怎么办呢?不要紧,咱们还有@Qulifier!首先咱们须要使用Qulifier定义两个注解:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierA { }复制代码
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierB { }复制代码

同时咱们须要对依赖提供方作出修改

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    @QualifierA
    @Provides
    Engine provideEngineA(){
        return new Engine("gearA");
    }

    @QualifierB
    @Provides
    Engine provideEngineB(){
        return new Engine("gearB");
    }
}复制代码

接下来依赖需求方Car类一样须要修改

public class Car {

    @QualifierA @Inject Engine engineA;
    @QualifierB @Inject Engine engineB;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngineA() {
        return this.engineA;
    }

    public Engine getEngineB() {
        return this.engineB;
    }
}复制代码

最后咱们再对Engine类作些调整方便测试

public class Engine {

    private String gear;

    public Engine(String gear){
        this.gear = gear;
    }

    public void printGearName(){
        System.out.println("GearName:" + gear);
    }
}复制代码

测试代码

public static void main(String[] args) {
    Car car = new Car();
    car.getEngineA().printGearName();
    car.getEngineB().printGearName();
}复制代码

执行结果:

GearName:gearA
GearName:gearB复制代码

四、案例D

接下来咱们看看@Scope是如何限定做用域,实现局部单例的。

首先咱们须要经过@Scope定义一个CarScope注解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CarScope {
}复制代码

接着咱们须要用这个@CarScope去标记依赖提供方MarkCarModule。

@Module
public class MarkCarModule {

    public MarkCarModule() {
    }

    @Provides
    @CarScope
    Engine provideEngine() {
        return new Engine("gear");
    }
}复制代码

同时还须要使用@Scope去标注注入器Compoent

@CarScope
@Component(modules = {MarkCarModule.class})
public interface CarComponent {
    void inject(Car car);
}复制代码

为了便于测试咱们对Car和Engine类作了一些改造:

public class Car {

    @Inject Engine engineA;
    @Inject Engine engineB;

    public Car() {
        DaggerCarComponent.builder()
                .markCarModule(new MarkCarModule())
                .build().inject(this);
    }
}复制代码
public class Engine {

    private String gear;

    public Engine(String gear){
        System.out.println("Create Engine");
        this.gear = gear;
    }
}复制代码

若是咱们不适用@Scope,上面的代码会实例化两次Engine类,所以会有两次\"Create Engine\"输出。如今咱们在有@Scope的状况测试下劳动成果:

public static void main(String[] args) {
    Car car = new Car();
    System.out.println(car.engineA.hashCode());
    System.out.println(car.engineB.hashCode());
}复制代码

输出

Create Engine复制代码

bingo!咱们确实经过@Scope实现了局部的单例。

Dagger2原理分析

前面啰里啰嗦的介绍了Dagger2的基本使用,接下来咱们再分析分析实现原理。这里不会分析Dagger2根据注解生成各类代码的原理,关于Java注解之后有机会再写一篇文章来介绍。后面主要分析的是Dagger2生成的各类类如何帮咱们实现依赖注入,为了便于理解我这里选了前面相对简单的案例B来作分析。

Dagger2编译期生成的代码位于build/generated/source/apt/debug/your package name/下面:

首先咱们看看Dagger2依据依赖提供方MarkCarModule生成的对应工厂类MarkCarModule_ProvideEngineFactory。为了方便你们理解对比,后面我一概会把本身写的类和Dagger2生成的类一并放出来。

/** * 咱们本身的类 */
@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    @Provides Engine provideEngine(){
        return new Engine("gear");
    }
}

/** * Dagger2生成的工厂类 */
public final class MarkCarModule_ProvideEngineFactory implements Factory<Engine> {
  private final MarkCarModule module;

  public MarkCarModule_ProvideEngineFactory(MarkCarModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public Engine get() {
    return Preconditions.checkNotNull(
        module.provideEngine(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<Engine> create(MarkCarModule module) {
    return new MarkCarModule_ProvideEngineFactory(module);
  }

  /** Proxies {@link MarkCarModule#provideEngine()}. */
  public static Engine proxyProvideEngine(MarkCarModule instance) {
    return instance.provideEngine();
  }
}复制代码

咱们能够看到MarkCarModule_ProvideEngineFactory中的get()调用了MarkCarModuleprovideEngine()方法来获取咱们须要的依赖EngineMarkCarModule_ProvideEngineFactory的实例化有crate()建立,而且MarkCarModule的实例也是经过create()方法传进来的。那么这个create()必定会在哪里调用的,咱们接着往下看。

前面提到@Component是依赖提供方(MarkCarModule)和依赖需求方(Car)以前的桥梁,那我看看Dagger2是如何经过CarComponent将二者联系起来的。

/** * 咱们本身的类 */
@Component(modules = {MarkCarModule.class})
public interface CarComponent {

    void inject(Car car);
}

/** * Dagger2生成的CarComponent实现类 */
public final class DaggerCarComponent implements CarComponent {
  private Provider<Engine> provideEngineProvider;

  private MembersInjector<Car> carMembersInjector;

  private DaggerCarComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static CarComponent create() {
    return builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.provideEngineProvider = MarkCarModule_ProvideEngineFactory.create(builder.markCarModule);
    this.carMembersInjector = Car_MembersInjector.create(provideEngineProvider);
  }

  @Override
  public void inject(Car car) {
    carMembersInjector.injectMembers(car);
  }

  public static final class Builder {
    private MarkCarModule markCarModule;

    private Builder() {}

    public CarComponent build() {
      if (markCarModule == null) {
        this.markCarModule = new MarkCarModule();
      }
      return new DaggerCarComponent(this);
    }

    public Builder markCarModule(MarkCarModule markCarModule) {
      this.markCarModule = Preconditions.checkNotNull(markCarModule);
      return this;
    }
  }
}复制代码

经过上面的代码咱们看到Dagger2依据CarComponent接口生成了实现类DaggerCarComponent(没错这正是咱们在Car的构造函数中使用DaggerCarComponent)。DaggerCarComponent在build的时候实例化了DaggerCarComponent对象,并首先调用MarkCarModule_ProvideEngineFactory.create(builder.markCarModule)始化了provideEngineProvider变量,接着调用Car_MembersInjector.create(provideEngineProvider)初始化了carMembersInjector变量。当咱们手动在Car类的构造函数中调用inject(Car car)方法时会执行carMembersInjector.injectMembers(car)。因此接下来咱们要看看Car_MembersInjector的实现。

public final class Car_MembersInjector implements MembersInjector<Car> {
  private final Provider<Engine> engineProvider;

  public Car_MembersInjector(Provider<Engine> engineProvider) {
    assert engineProvider != null;
    this.engineProvider = engineProvider;
  }

  public static MembersInjector<Car> create(Provider<Engine> engineProvider) {
    return new Car_MembersInjector(engineProvider);
  }

  @Override
  public void injectMembers(Car instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.engine = engineProvider.get();
  }

  public static void injectEngine(Car instance, Provider<Engine> engineProvider) {
    instance.engine = engineProvider.get();
  }
}复制代码

Car_MembersInjector中的create()用于实例化本身,这个方法前面咱们看到是在DaggerCarComponent中调用的。injectMembers(Car instance)engineProvider.get()的返回值赋给了依赖需求方Car的engine变量,而engineProvider.get()正是本节一开始咱们提到的MarkCarModule_ProvideEngineFactory中的get()方法。至此整个依赖注入的流程就完成了。更复杂的应用场景会生成更加复杂的代码,但原理都和前面分析的大同小异。

总结

这篇文章只是经过一些简单的例子介绍了Dagger2的相关概念及使用,实际项目中的应用远比这里的例子要复杂。关于Dagger2在实际项目中的应用能够参照这个开源项目 github.com/BaronZ88/Mi…(项目采用MVP架构,其中View层和Presenter层的解耦就是经过Dagger2来实现的)。

MinimalistWeather是一款开源天气App,开发此项目主要是为展现各类开源库的使用方式以及Android项目的架构方案,并做为团队开发规范的一部分。项目中每个字母、每个命名、每一行代码都是通过仔细考究的;可是因为时间精力有限,项目UI未作严格要求。本着精益求精、提供更好开源项目和更美天气应用的原则,所以指望有兴趣的开发和UED同窗能够一块儿来完成这个项目。

本次掘金征文活动连接gold.xitu.io/post/58522d…

相关文章
相关标签/搜索