Android -- 带你从源码角度领悟Dagger2入门到放弃(二)

时间:2022-11-27 06:05:28

1,接着我们上一篇继续介绍,在上一篇我们介绍了简单的@Inject和@Component的结合使用,现在我们继续以老师和学生的例子,我们知道学生上课的时候都会有书籍来辅助听课,先来看看我们之前的Student代码

package com.qianmo.rxjavatext;

import android.util.Log;

import javax.inject.Inject;

/**
 * Created by Administrator on 2017/4/17 0017.
 * E-Mail:543441727@qq.com
 */

public class Student {
    private int id;
    private String name;
    private Course[] course;

    @Inject
    public Student() {
        System.out.println("Student create!!!");
    }

    public Student(int id, String name, Course[] course) {
        this.id = id;
        this.name = name;
        this.course = course;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Course[] getCourse() {
        return course;
    }

    public void setCourse(Course[] course) {
        this.course = course;
    }


    public void startLessons() {
        System.out.println("开始上课了");
    }
}

  添加书籍对象,在类中有书籍的姓名属性,还有变黑动作(这里是瞎加上这个动作的),Book的代码如下:

/**
 * Created by Administrator on 2017/4/20 0020.
 * E-Mail:543441727@qq.com
 */

public class Book {

    private String name;

    public Book(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void changeBlack() {
        System.out.println(name + "我一本新书被翻黑了。。。。");
    }
}

  然后我们student中的构造函数也要添加了如下代码,且以前的构造函数需要删掉

package com.qianmo.rxjavatext;

import android.util.Log;

import javax.inject.Inject;

/**
 * Created by Administrator on 2017/4/17 0017.
 * E-Mail:543441727@qq.com
 */

public class Student {
    private int id;
    private String name;
    private Course[] course;
    private Book book;

    @Inject
    public Student(Book book) {
        System.out.println("Student create with book!!!");
    }

    public Student(int id, String name, Course[] course) {
        this.id = id;
        this.name = name;
        this.course = course;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Course[] getCourse() {
        return course;
    }

    public void setCourse(Course[] course) {
        this.course = course;
    }
    public void startLessons() {
        System.out.println("开始上课了");
        book.changeBlack();
    }
}

  这时候我们会有一个猜想,当我们一个类中存在两个构造函数被标记了@Inject标记呢,那我们来试试代码如下:

  @Inject
    public Student() {
        System.out.println("Student create!!!");
    }

    @Inject
    public Student(Book book) {
        System.out.println("Student create with book!!!");
    }

  但是你最后运行的时候会直接报错了,报错信息如下,翻译过来意思就是说Student类中只能包含一个@Inject标记修饰构造函数

Error:(19, 12) 错误: Types may only contain one @Inject constructor.

  OK,我们肯定现在脑袋了有这种疑问,要是我真的是想使用两个或者多个构造函数呢,别急,后面会和大家在实例中来讲解怎么使用的,好了还是回到我们正题上来,现在我们Student构造函数中出现了一个book对象,我们可能会想,有可能Dagger2会帮我们继续new一个Book对象放入Student构造函数中呐,好,我们抱着它能有这么智能我们来运行一下工程。

  哦哦,sorry,报错了,我们来看一下报错提示,说不定我们能从报错信息来找到解决方法呐

Error:(12, 9) Gradle: 错误: com.qianmo.rxjavatext.Book cannot be provided without an @Inject constructor or from an @Provides-annotated method.

  从上面你的信息我们知道它说book对象中没有提供一个标注为@Inject的构造方法,或者@Provides的方法。

  好的我们就先用第一种方法试试,标记Book构造函数为@Inject,并修改在Student类中的成员变量book也标记成@Inject,运行一下项目,运行结构如下

Student create with book!!!
开始上课了
我一本新书被翻黑了。。。。

  呃,可以了,没想到就这样可以了,666啊  ,这是我们使用的第一个方法,我们现在根据它之前报错信息的第二个方法来实现提供一个@Provides标记的方法

2,@Module和@Provides的使用

   而使用@Provides标记就要使用@Module标签了,先来回顾一下这两个注解标签的定义

@module,标注用于提供需要注入的实例的类,当我们要提供第三方的依赖时,使用Inject注解类的构造函数很明显不现实,这时就可以使用这个注解,在module中提供。
@Provides,使用在用@module标注的类里面,告诉dagger2来这里找依赖。

  除了构造函数提供依赖,还能用Module提供。所以这里我们要创建一个Module,并创建@Provides注解标签修饰的对应的提供该对象的方法

@Module
public class TeacherModule {

    @Provides
    Book provideBook() {
        return new Book();
    }
}

  这里需要解释一波了,首先provideBook这个方法名是固定的吗,当然不是固定的,不过我们为了逻辑清晰,一般采用然后修改provide开头,后面再加上类名,切记一定要加上@Provide标签,要让桥接器知道在这里找依赖。

  然后要修改TeacherComponent中的module引用

package com.qianmo.rxjavatext;

import dagger.Component;

/**
 * Created by Administrator on 2017/4/20 0020.
 * E-Mail:543441727@qq.com
 */
@Component(modules = TeacherModule.class)
public interface TeacherComponent {
    void injectA(Teacher teacher);
}

  修改调用方法

DaggerTeacherComponent.builder().teacherModule(new TeacherModule()).build().injectA(this);

  看一下运行效果

Student create with book!!!
开始上课了
我一本新书被翻黑了。。。。

  OK,打印结果没问题,这里我们想着既然Module能够提供依赖,那么我们把之前的Student的构造函数依赖添加到这里,代码如下:

package com.qianmo.rxjavatext;

import dagger.Module;
import dagger.Provides;

/**
 * Created by Administrator on 2017/4/20 0020.
 * E-Mail:543441727@qq.com
 */

@Module
public class TeacherModule {

    @Provides
    Book provideBook() {
        return new Book();
    }

    @Provides
    Student provideStudent(Book book){
        return new Student(book);
    }
}

  这时候我们会有一个疑问,我们在Student构造函数加过@Inject注解又在Module中提供了依赖,这两个会不会冲突啊,我们心里面先带着这个疑问,运行一下项目,发现没什么问题运行结果如下:

Student create with book!!!
开始上课了
我一本新书被翻黑了。。。。

  那么我们现在会有一个疑问了,到底我们是我们通过@Inject注解标记Student构造函数起了作用还是我们的Module中的provideStudent方法起作用了呢?那么是时候看一波我们的源码了,我们这次一共涉及到四个类DaggerTeacherComponent、Teacher_MembersInjector、TeacherModule_ProvideStudentFactory、TeacherModule_ProvideBookFactory,前面两个类和我们之前一篇文章源码分析类似,后面两个类从之前的StudentFactory变成了TeacherModule_ProvideStudentFactory、TeacherModule_ProvideBookFactory这两个类,从字面上我们也可以理解一个是从TeacherModule中获取Student对象的提供工厂,一个是从TeacherModule中获取Book对象的提供工厂。不多说,我们继续先来看看DaggerTeacherComponent类的源码

package com.qianmo.rxjavatext;

import dagger.MembersInjector;
import dagger.internal.Preconditions;
import javax.annotation.Generated;
import javax.inject.Provider;

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class DaggerTeacherComponent implements TeacherComponent {
  private Provider<Book> provideBookProvider;

  private Provider<Student> provideStudentProvider;

  private MembersInjector<Teacher> teacherMembersInjector;

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

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

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

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

    this.provideBookProvider = TeacherModule_ProvideBookFactory.create(builder.teacherModule);

    this.provideStudentProvider =
        TeacherModule_ProvideStudentFactory.create(builder.teacherModule, provideBookProvider);

    this.teacherMembersInjector = Teacher_MembersInjector.create(provideStudentProvider);
  }

  @Override
  public void injectA(Teacher teacher) {
    teacherMembersInjector.injectMembers(teacher);
  }

  public static final class Builder {
    private TeacherModule teacherModule;

    private Builder() {}

    public TeacherComponent build() {
      if (teacherModule == null) {
        this.teacherModule = new TeacherModule();
      }
      return new DaggerTeacherComponent(this);
    }

    public Builder teacherModule(TeacherModule teacherModule) {
      this.teacherModule = Preconditions.checkNotNull(teacherModule);
      return this;
    }
  }
}

  可以看到和我们之前的DaggerTeacherComponent类源码有所不同,多了teacherModule、provideBookProvider、provideStudentProvider三个成员变量,多了teacherModule()方法。

  ok,继续按照我们之前的方法分析首先调用DaggerTeacherComponent.builder()创建出一个Builder对象出来,再调用DaggerTeacherComponent.builder().teacherModule(new TeacherModule()),注意了,请看teacherModule()方法里面的源码

public Builder teacherModule(TeacherModule teacherModule) {
      this.teacherModule = Preconditions.checkNotNull(teacherModule);
      return this;
    }

  这里先检查传递的teacherModule对象是否为空,让不为空的话将将值赋值到成员变量this.teacherModule上。

  OK,我们继续往下看DaggerTeacherComponent.builder().teacherModule(new TeacherModule()).build(),接下来调用而是build方法,来看看具体的源码

public TeacherComponent build() {
      if (teacherModule == null) {
        this.teacherModule = new TeacherModule();
      }
      return new DaggerTeacherComponent(this);
    }

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

private void initialize(final Builder builder) {

    this.provideBookProvider = TeacherModule_ProvideBookFactory.create(builder.teacherModule);

    this.provideStudentProvider =
        TeacherModule_ProvideStudentFactory.create(builder.teacherModule, provideBookProvider);

    this.teacherMembersInjector = Teacher_MembersInjector.create(provideStudentProvider);
  }

  从上面你的源码可以看到,首先我们在build方法判断teacherModule属性是否为空,如果为空则自己new一个TeacherModule对象(那这里是不是表示我们之前的teacherModule(new TeacherModule())方法可以偷懒不用写?  大家可以去试一试,的确可以的,手动微笑....),然后调用DaggerTeacherComponent类中的构造函数,在构造函数中调用initialize()方法,好了这里是重点了,在这里我们赋值了provideBookProvider、provideStudentProvider两个成员对象,所以我们现在看看TeacherModule_ProvideBookFactory 、TeacherModule_ProvideStudentFactory中的create方法干了什么

package com.qianmo.rxjavatext;

import dagger.internal.Factory;
import dagger.internal.Preconditions;
import javax.annotation.Generated;

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class TeacherModule_ProvideBookFactory implements Factory<Book> {
  private final TeacherModule module;

  public TeacherModule_ProvideBookFactory(TeacherModule module) {
    assert module != null;
    this.module = module;
  }

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

  public static Factory<Book> create(TeacherModule module) {
    return new TeacherModule_ProvideBookFactory(module);
  }
}

  

package com.qianmo.rxjavatext;

import dagger.internal.Factory;
import dagger.internal.Preconditions;
import javax.annotation.Generated;
import javax.inject.Provider;

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class TeacherModule_ProvideStudentFactory implements Factory<Student> {
  private final TeacherModule module;

  private final Provider<Book> bookProvider;

  public TeacherModule_ProvideStudentFactory(TeacherModule module, Provider<Book> bookProvider) {
    assert module != null;
    this.module = module;
    assert bookProvider != null;
    this.bookProvider = bookProvider;
  }

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

  public static Factory<Student> create(TeacherModule module, Provider<Book> bookProvider) {
    return new TeacherModule_ProvideStudentFactory(module, bookProvider);
  }
}

  大家请看这两个类中的get方法中的参数!!!!  首先TeacherModule_ProvideBookFactory中调用的是module.provideBook(),获取到我们的book创建的对象,在看TeacherModule_ProvideStudentFactory中的get方法首先拿到TeacherModule_ProvideStudentFactory中的book对象,然后在调用 module.provideStudent(bookProvider.get())方法,拿到TeacherModule中创建的Student对象。

  ok现在基本流程清楚了,最后调用injectA(this)方法在Teacher_MembersInjector类中把当前的Teacher对象中的student属性被赋值到TeacherModule_ProvideStudentFactory.get()上去,这样我们的赋值就完成了。

  那么这里我们可以有一下结论

我们有两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Module里提供的依赖,那么Dagger2是怎么选择依赖提供的呢,规则是这样的:

步骤1:查找Module中是否存在创建该类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数

步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

概括一下就是从注解了@Inject的对象开始,从Module和注解过的构造方法中获得实例,若在获取该实例的过程中需要其他类的实例,则继续获取被需要类的实例对象的依赖,同样是从Module和标注过的构造方法中获取,并不断递归这个过程直到所有被需要的类的实例创建完成,在这个过程中Module的优先级高于@Inject注解过的构造方法。

  这样我们就懂了为什么同事存在Module和@Inject都不会报错,且它对Module和@Inject提供对象实例的优先级关系了。

3、@Qulifier的使用

  现在继续扩展业务,由于学校扩招,现在任课老师下面由之前的一个学生变成了两个学生,且新来的那个学生是没有书籍的,那么在我们java代码中怎么表示这种场景呢?

package com.qianmo.rxjavatext;

import javax.inject.Inject;

/**
 * Created by Administrator on 2017/4/20 0020.
 * E-Mail:543441727@qq.com
 */

public class Teacher {
    //想持有学生对象
    @Inject
    Student student1; //带了书的

    @Inject
    Student student2; //没带了书的

    public Teacher() {
        DaggerTeacherComponent.builder().teacherModule(new TeacherModule()).build().injectA(this);
    }

    public void teacher() {
        student1.startLessons();
        student2.startLessons();
    }

    public static void main(String[] args) {
        new Teacher().teacher();
    }
}

  自该修改Student中的构造函数,添加不带书的构造方法

    public Student(Book book) {
        System.out.println("Student create with book!!!");
        this.book = book;
    }

    public Student() {
        System.out.println("Student create without book!!!");
        this.book = new Book("对不起我没有书啊");
    }

  这时候我们想是想student2获取的是Student()的无参构造的对象,所以我们相当然的在我们的Module中添加对应的提供方法,代码如下:

package com.qianmo.rxjavatext;

import dagger.Module;
import dagger.Provides;

/**
 * Created by Administrator on 2017/4/20 0020.
 * E-Mail:543441727@qq.com
 */

@Module
public class TeacherModule {

    @Provides
    Book provideBook() {
        return new Book("我是真实的书籍呢...");
    }

    /**
     * 提供有书的学生
     * @param book
     * @return
     */
    @Provides
    Student provideStudentA(Book book) {
        return new Student(book);
    }

    /**
     * 提供没书的学生
     * @return
     */
    @Provides
    Student provideStudentB() {
        return new Student();
    }
}

  然后你开开心心的运行项目,会发现直接报错了,报错如下:

Error:(10, 9) Gradle: 错误: com.qianmo.rxjavatext.Student is bound multiple times:
@Provides com.qianmo.rxjavatext.Student com.qianmo.rxjavatext.TeacherModule.provideStudentA(com.qianmo.rxjavatext.Book)
@Provides com.qianmo.rxjavatext.Student com.qianmo.rxjavatext.TeacherModule.provideStudentB()

  明面字义翻译过来就是“绑定的时候迷失了自己在provideStudentA、provideStudentB方法之间”,我们专业术语叫“做依赖迷失”,因为Dagger2是根据返回类型来进行依赖注入的,但是这里我们提供了A、B两个方法返回相同的对象,这时候我们的student1、studnet2不知道他们自己到底是要创建有书的对象呢还是没有书的对象呢。

  so,为了解决这个问题,我们的Dagger2提供了@Qulifier,可以通过自定义注解,来告诉Dagger2我这次到底是想依赖那个方法,这里不会注解的同学可以去看一下我之前写的这篇文章,那我们就来创建StudentA和StudnetB自定义注解

package com.qianmo.rxjavatext;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Qualifier;

/**
 * Created by Administrator on 2017/4/21 0021.
 * E-Mail:543441727@qq.com
 */
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentA {
}

  

package com.qianmo.rxjavatext;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Qualifier;

/**
 * Created by Administrator on 2017/4/21 0021.
 * E-Mail:543441727@qq.com
 */
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentB {
}

  然后在Module中添加这个注解

/**
     * 提供有书的学生
     * @param book
     * @return
     */
    @StudentA
    @Provides
    Student provideStudentA(Book book) {
        return new Student(book);
    }

    /**
     * 提供没书的学生
     * @return
     */
    @StudentB
    @Provides
    Student provideStudentB() {
        return new Student();
    }

  在需要使用student对象的地方添加

 //想持有学生对象
    @Inject
    @StudentA
    Student student1; //带了书的

    @Inject
    @StudentB
    Student student2; //没带了书的

  ok,运行一下项目,看一下打印效果

Student create with book!!!
Student create without book!!!
开始上课了
我一本新书被翻黑了。。。。我是真实的书籍呢...
开始上课了
我一本新书被翻黑了。。。。对不起我没有书啊

  没问题,这里我们Dagger2为了让我们使用方便实际上是提供了一个@Name标签供我们使用的,看一下它里面的内容

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

  在底层也是使用了我们@Qualifier标签的,所以如果使用@Name标签来实现我们上面的效果的话是这样的

 /**
     * 提供有书的学生
     * @param book
     * @return
     */
    @Provides
    @Named("StudentA")
    Student provideStudentA(Book book) {
        return new Student(book);
    }

    /**
     * 提供没书的学生
     * @return
     */
    @Provides
    @Named("StudentB")
    Student provideStudentB() {
        return new Student();
    }

  

 //想持有学生对象
    @Inject
    @Named("StudentA")
    Student student1; //带了书的

    @Inject
    @Named("StudentB")
    Student student2; //没带了书的

  哈哈哈,看到这儿有没有同学想说我是马后炮的(其实,只能我们自己知道了它是怎么实现的才能更好的使用),说你直接介绍@Name标签不就行了,但是不利于我们之后的深层次的学习。

4,@Scope的使用

  这个标签不是很好理解,给的解释是该注解能够使同一个Component中的对象保持唯一,即单例(一定要记住是局部单例)。

  那么我们来写一个栗子试试

  首先继续自定义注解,并添加

package com.qianmo.rxjavatext;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.inject.Qualifier;
import javax.inject.Scope;

/**
 * Created by Administrator on 2017/4/21 0021.
 * E-Mail:543441727@qq.com
 */
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentOnlyOne {
}

  在B中添加方法标签的引用

 /**
     * 提供没书的学生
     * @return
     */
    @Provides
    @StudentOnlyOne
    @Named("StudentB")
    Student provideStudentB() {
        return new Student();
    }

  修改Teacher中的两个学生变量的使用,都是用B方法提供Student对象

    @Inject
    @Named("StudentB")
    Student student1; //带了书的

    @Inject
    @Named("StudentB")
    Student student2; //没带了书的

  最后,很重要!!!在你的component中添加上面标注!!!!,不然你就等着bug满天飞吧(在这儿趟坑了很久)

package com.qianmo.rxjavatext;

import dagger.Component;

/**
 * Created by Administrator on 2017/4/20 0020.
 * E-Mail:543441727@qq.com
 */
@StudentOnlyOne
@Component(modules = TeacherModule.class)
public interface TeacherComponent {
    void injectA(Teacher teacher);
}

  ok,运行一下,看一下打印效果,

Student create without book!!!
开始上课了
我一本新书被翻黑了。。。。对不起我没有书啊
开始上课了
我一本新书被翻黑了。。。。对不起我没有书啊

  书的确只被创建了一次,ok,其实我们Dagger2中也提供了封装好了的@Scope,就是我们的@Singleton,看一下它的源码

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

  也是应用了@Scope标签,对不起,又马后炮了一次(哈哈哈........)

  到这里我们的Dagger2的标签基本上就全部学习完了,后面一篇我将和大家一起看看,在Android中的Activity ,Dagger2能帮我们做些什么

  最后!!!还有一个很关键事:最近打算辞职回北京,有没有大神同学推荐工作,带带啊啊啊啊啊

   下一篇:Android -- 带你从源码角度领悟Dagger2入门到放弃(三)