从源码角度分析注解在Dagger2中的使用

前言

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

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

Dagger2注解

Dagger2是基于Java注解来实现依赖注入的,那么在正式使用以前咱们须要先了解下Dagger2中的注解。Dagger2使用过程当中咱们一般接触到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。ide

  • @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标注的构造函数,看构造函数是否存在参数。ui

    • a:若存在参数,则从步骤1开始依次初始化每个参数
    • b:若不存在,则直接初始化该类实例,完成一次依赖注入。

Dagger2使用入门

一、案例A

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

public class Engine {

    /** * 二是用来标记构造函数,Dagger2经过@Inject注解能够在须要这个类实例的时候来找到这个构造函数并把相关实例构造出来, * 以此来为被@Inject标记了的变量提供依赖 */
    @Inject
    Engine(){}

    @Override
    public String toString() {
        return "Engine{}";
    }

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

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

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

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

public class Car {
    /** * @Inject@Inject有两个做用,一是用来标记须要依赖的变量,以此告诉Dagger2为它提供依赖 */
    @Inject
    Engine engine;

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

    public Engine getEngine() {
        return this.engine;
    }

    public static void main(String ... args){
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngine());
    }
}
复制代码

二、案例B

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

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

public class Engine {

    private String name;

    @Inject
    Engine(){}

    Engine(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Engine{" +
                "name='" + name + '\'' +
                '}';
    }

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

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

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    /** * 用于标注Module所标注的类中的方法,该方法在须要提供依赖时被调用,从而把预先提供好的对象当作依赖给标注了@Inject的变量赋值 * @return */
    @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())方法,这就至关于告诉了注入器DaggerCarComponent把MarkCarModule提供的依赖注入到了Car类中。

public class Car {
    /** * 咱们提到@Inject@Module均可以提供依赖,那若是咱们即在构造函数上经过标记@Inject提供依赖,有经过@Module提供依赖Dagger2会如何选择呢?具体规则以下: * * 步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。 * 步骤2:若存在提供依赖的方法,查看该方法是否存在参数。 * a:若存在参数,则按从步骤1开始依次初始化每一个参数; * b:若不存在,则直接初始化该类实例,完成一次依赖注入。 * * * 步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。 * a:若存在参数,则从步骤1开始依次初始化每个参数 * b:若不存在,则直接初始化该类实例,完成一次依赖注入 */
    @Inject
    Engine engine;

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

    public Engine getEngine() {
        return this.engine;
    }

    public static void main(String ... args){
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngine());
    }
}
复制代码

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

Engine{name='gear'}
复制代码

三、案例C

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

public class Engine {

    /** * 用于自定义注解,也就是说@Qulifier就如同Java提供的几种基本元注解同样用来标记注解类。咱们在使用@Module来标注提供依赖的方法时,方法名咱们是能够随便定义的(虽然咱们定义方法名通常以provide开头,但这并非强制的,只是为了增长可读性而已)。那么Dagger2怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2根据返回值的类型来决定为哪一个被@Inject标记了的变量赋值。可是问题来了,一旦有多个同样的返回类型Dagger2就懵逼了。@Qulifier的存在正式为了解决这个问题,咱们使用@Qulifier来定义本身的注解,而后经过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject标注的变量),这样Dagger2就知道为谁提供依赖了。----一个更为精简的定义:当类型不足以鉴别一个依赖的时候,咱们就可使用这个注解标示 * 1. 使用@Qulifier定义两个注解 */
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QualifierA { }
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QualifierB { }

    private String name;

    Engine(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Engine{" +
                "name='" + name + '\'' +
                '}';
    }

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

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

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    /** * 2. 同时咱们须要对依赖提供方作出修改 * @return */
    @Engine.QualifierA
    @Provides
    Engine provideEngineA(){
        return new Engine("gearA");
    }

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

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

public class Car {
    /** * 3. 接下来依赖需求方Car类一样须要修改 */
    @Engine.QualifierA
    @Inject
    Engine engineA;

    @Engine.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;
    }

    public static void main(String... args) {
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngineA());
        System.out.println(car.getEngineB());
    }
}
复制代码

执行结果:

Engine{name='gearA'}
Engine{name='gearB'}
复制代码

四、案例D

接下来咱们看看@Scope是如何限定做用域,实现局部单例的。 首先咱们须要经过@Scope定义一个CarScope注解:

public class Engine {


    /** * 用于自定义注解,我能能够经过@Scope自定义的注解来限定注解做用域,实现局部的单例 * 1. @Scope定义一个CarScope注解 */
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CarScope {
    }

    private String name;

    Engine(String name) {
        this.name = name;
        System.out.println("Engine create: " + name);
    }

    @Override
    public String toString() {
        return "Engine{" +
                "name='" + name + '\'' +
                '}';
    }

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

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

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    /** * 2. @CarScope去标记依赖提供方MarkCarModule * @return */
    @Engine.CarScope
    @Provides
    Engine provideEngine(){
        return new Engine("gear");
    }

}
复制代码

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

/** * 3. 同时还须要使用@Scope去标注注入器Compoent */
@Engine.CarScope
@Component(modules = MarkCarModule.class)
public interface CarComponent {
    void inject(Car car);
}
复制代码
public class Car {

    @Inject
    Engine engineA;

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

    public static void main(String... args) {
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngineA());
        System.out.println(car.getEngineB());
    }
}
复制代码

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

Engine create: gear
Engine{name='gear'}
Engine{name='gear'}
复制代码

最后

读到这的朋友以为不错能够点赞关注下,感谢您的支持,之后会不停更新更多精选干货及资讯分享,欢迎你们在评论区留言讨论!

相关文章
相关标签/搜索