zoukankan      html  css  js  c++  java
  • Android -- 带你从源码角度领悟Dagger2入门到放弃(二)

    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入门到放弃(三)

  • 相关阅读:
    PHP 错误:Warning: Cannot modify header information
    PHP截取中文字符串
    myeclipse 保存含中文的jsp失败,提示内容含有 ISO-8859-1 不支持的字符
    jquery ajax到servlet出现中文乱码(utf-8编码下)
    数据结构~动态存储管理(五)
    数据结构~树和二叉树(三)
    数据结构~线性表(二)
    数据结构~基础概念(一)
    每日一摘:串并-并串转换
    每日一摘:Verilog复位
  • 原文地址:https://www.cnblogs.com/wjtaigwh/p/6740643.html
Copyright © 2011-2022 走看看