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