Dagger ,早有耳闻,但一直没有与他正真的认识过。Dagger 英文翻译为“匕首”,这可能就是把它称作神兵利器的原因。当我看一些开源项目在使用dagger2
时,我停止了前进的步伐,查阅了网上一些层差不齐的资料后,感觉还是懵逼状态,正当我感受到了从入门到放弃的状态时,看到了一篇相对好理解一写的博文,我又开始踏上了从放弃到入门之路。
背景(日常念经,了解一下)
Dagger ,鼎鼎大名的Square公司 开发的,光听Square,就知道肯定不简单(okhttp
,Picasso,leakcanary
,等等等等)起初Square公司受到Guice
的启发而开发了Dagger,但是Dagger这种半静态半运行时的框架还是有些性能问题(虽说依赖注入是完全静态的,但是其有向无环图(Directed Acyclic Graph)还是基于反射来生成的,这无论在大型的服务端应用还是在Android应用上都不是最优方案)。因此Google工程师Fork了Dagger项目,对它进行了改造。于是变演变出了今天我们要讨论的Dagger2,所以说Dagger2其实就是高配版的Dagger。
Dagger2 基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。
依赖注入
上文中提到了Dagger2 是解决Android或java中依赖注入的一个类库(Dependency Injection类库) ,通过注解在编译期生成代码的方式实现注入。那为什么要用依赖注入呢?首先看个例子
上面两张图很形象的说明了使用依赖的差别,只不过用的是构造函数传参给成员变量赋值,实现注入(后面简称构造注入)。前者在编码中肯定不提倡,因为Major和 Student耦合性太高。当然,除了构造函数注入还有接口注入:实现接口方法,同样以传参的方式实现注入。
public interface Injection<T>{
void inject(T t);
}
public class Student implements Injection<Engine>{
private Major major;
public Student(){}
public void inject(Major major){
this.major = major;
}
}
重点来了当然,前两者注入方式需要编写大量代码,Dagger2是通过Java注解在编译期来实现依赖注入的
/**
* 创建时间:2018/8/23
* 编写人:kanghb
* 功能描述:学生
*/
public class Student {
@Inject
Major major;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"major=" + major +
'}';
}
}
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标注的接口为StudentComponent,则编译期生成的实现类为DaggerStudentComponent),我们通过调用这个实现类的方法完成注入;
@Qulifier:@Qulifier用于自定义注解,也就是说@Qulifier就如同Java提供的几种基本元注解一样用来标记注解类。我们在使用@Module来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以provide开头,但这并不是强制的,只是为了增加可读性而已)。那么Dagger2怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2根据返回值的类型来决定为哪个被@Inject标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型Dagger2就懵逼了。@Qulifier的存在正式为了解决这个问题,我们使用@Qulifier来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject标注的变量),这样Dagger2就知道为谁提供依赖了。—-一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;
@Scope:@Scope同样用于自定义注解,我能可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;
@Singleton:@Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来实现全局单例。但实际上它并不能t体现全局单例,是否能提供全局单例还要取决于对应的Component是否为一个全局对象。
我们提到@Inject和@Module都可以提供依赖,那如果我们即在构造函数上通过标记@Inject提供依赖,有通过@Module提供依赖Dagger2会如何选择呢?具体规则如下:
Dagger2使用
一、@inject标注构造方法提供依赖
1.添加依赖
// Add Dagger dependencies
dependencies {
compile 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}
关于Dagger2的依赖配置就不在这里占用篇幅去描述了,大家可以到它的github主页下去查看官方教程https://github.com/google/dagger。接下来我们还是拿前面的Car和Engine来举例。
2.Student类是依赖需求方,依赖了Major类;因此我们需要在类变量Major上添加@Inject来告诉Dagger2来为自己提供依赖。
/**
* 创建时间:2018/8/23
* 编写人:kanghb
* 功能描述:学生(依赖需求方)
*/
public class Student {
@Inject
Major major;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"major=" + major +
'}';
}
}
3.Major类是依赖提供方,因此我们需要在它的构造函数上添加@Inject
/**
* 创建时间:2018/8/23
* 编写人:kanghb
* 功能描述:专业(依赖提供方)
*/
public class Major {
@Inject
public Major() {
}
public void test(){
System.out.println("我是Major的方法");
}
}
4.创建一个用@Component标注的接口StudentComponent
,这个StudentComponent
其实就是一个注入器,这里用来将Major注入到Student中。
/**
* 创建时间:2018/8/24
* 编写人:kanghb
* 功能描述:
*/
@Component
public interface StudentComponent {
void bind(Student student);
}
注意:看一些文档里这里bind方法命名都是用inject的,我不知道是不是强制必须inject,所以写了bind看看测试一下,稍后揭晓结果。
5.上述操作完成后Build下项目,让Dagger2帮我们生成相关的Java类。
6.Student的构造函数中调用Dagger2生成的DaggerStudentComponent
来实现注入。
Student类完整代码如下
/**
* 创建时间:2018/8/23
* 编写人:kanghb
* 功能描述:学生(需求依赖方)
*/
public class Student {
@Inject
Major major;
public Student() {
DaggerStudentComponent.builder().build().bind(this);
}
@Override
public String toString() {
return "Student{" +
"major=" + major +
'}';
}
public Major getMajor() {
return major;
}
}
7.查看运行结果如下,证明我们前面说的inject方法不是强制的但有益于提升代码的可读性)
Student student = new Student();
student.getMajor().test();
08-24 01:57:58.035 2459-2459/kanghb.com.dagger2demo I/System.out: 我是Major的方法
二、@Module +@Provide标注构造方法提供依赖
如果创建Major的构造函数是带参数的呢?比如说制造一各专业是需要教师(teacher)的。或者Major类是我们无法修改的呢(依赖第三方,无法修改构造函数)?这时候就需要@Module和@Provide上场了。
1.在Student类的成员变量Major上加上@Inject表示自己需要Dagger2为自己提供依赖;Major类的构造函数上的@Inject也需要去掉,因为现在不需要通过构造函数上的@Inject来提供依赖了。
public class Student {
@Inject
Major major;
public Student() {
DaggerStudentComponent.builder().makeMajorModule(new MakeMajorModule()).build().inject(this);
}
@Override
public String toString() {
return "Student{" +
"major=" + major +
'}';
}
public Major getMajor() {
return major;
}
}
2.新建个Module类来生成依赖对象。前面介绍的@Module就是用来标准这个类的,而@Provide则是用来标注具体提供依赖对象的方法(这里有个不成文的规定,被@Provide标注的方法命名我们一般以provide开头,这并不是强制的但有益于提升代码的可读性)
@Module
public class MakeMajorModule {
public MakeMajorModule() {
}
@Provides
public Major provideMajor(){
return new Major("kanghanbin");
}
}
3.对StudentComponent进行一点点修改,之前的@Component注解是不带参数的,现在我们需要加上modules = {MakeMajorModule.class}
,用来告诉Dagger2提供依赖的是MakeMajorModule
这个类。
@Component(modules = {MakeMajorModule.class})
public interface StudentComponent {
void inject(Student student);
}
4.Student类的构造函数我们也需要修改,相比之前多了个markCarModule(new MarkCarModule())
方法,这就相当于告诉了注入器DaggerStudentComponent
把MakeMajorModule
提供的依赖注入到了Student类中
public Student() {
DaggerStudentComponent.builder().makeMajorModule(new MakeMajorModule()).build().inject(this);
}
注意:其实这里加不加 makeMajorModule(new MakeMajorModule())
方法,都能顺利执行,为什么呢,打开DaggerStudentComponent
一探究竟,发现就算不使用 makeMajorModule(new MakeMajorModule())
方法,在调用build方法时,也直接 new MakeMajorModule()。
public static final class Builder {
private MakeMajorModule makeMajorModule;
private Builder() {}
public StudentComponent build() {
if (makeMajorModule == null) {
this.makeMajorModule = new MakeMajorModule();
}
return new DaggerStudentComponent(this);
}
public Builder makeMajorModule(MakeMajorModule makeMajorModule) {
this.makeMajorModule = Preconditions.checkNotNull(makeMajorModule);
return this;
}
}
5.执行结果同样是
08-24 01:57:58.035 2459-2459/kanghb.com.dagger2demo I/System.out: 我是Major的方法
三、@Qualifier实现一个类中有两个相同类型不同对象
Dagger2根据返回值的类型来决定为哪个被@Inject标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型Dagger2就懵逼了。@Qulifier
的存在正式为了解决这个问题,我们使用@Qulifier
来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方。
1.使用Qulifier
定义两个注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierA {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierB {
}
2.对依赖提供方做出修改
@Module
public class MakeMajorModule {
public MakeMajorModule() {
}
@QualifierA
@Provides
public Major provideMajorA(){
return new Major("kanghanbin");
}
@QualifierB
@Provides
public Major provideMajorB(){
return new Major("fenglin");
}
}
3.对依赖需求方做出修改
public class Student {
@QualifierA
@Inject
Major major;
@QualifierB
@Inject
Major major2;
public Student() {
DaggerStudentComponent.builder().makeMajorModule(new MakeMajorModule()).build().inject(this);
}
@Override
public String toString() {
return "Student{" +
"major=" + major +
'}';
}
public Major getMajorA() {
return major;
}
public Major getMajorB() {
return major2;
}
4.添加测试代码查看运行结果
Student student = new Student();
student.getMajorA().test();
student.getMajorB().test();
08-24 03:32:20.678 7632-7632/kanghb.com.dagger2demo I/System.out: 我是teacher为kanghanbinMajor的方法
08-24 03:32:20.679 7632-7632/kanghb.com.dagger2demo I/System.out: 我是teacher为fenglinMajor的方法
四、@Scope限定作用域
1.首先我们需要通过@Scope定义一个StudentScope
注解:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentScope {
}
2.接着我们需要用这个@StudentScope
去标记依赖提供方MakeMajorModule
。
@StudentScope
@Provides
public Major provideMajorA(){
return new Major("kanghanbin");
}
3.用@StudentScope
去标注注入器 StudentComponent
@StudentScope
@Component(modules = {MakeMajorModule.class})
public interface StudentComponent {
void inject(Student student);
}
4.Student类改为
@Inject
Major major;
@Inject
Major major2;
5.Major类改为
public Major(String teacher) {
this.teacher = teacher;
System.out.println("Create a Major");
}
public void test(){
System.out.println("我是teacher为"+ teacher + "Major的方法");
}
输出结果:
Create a Major
但是如果我么不加@StudentScope
,就打印出两次
Create a Major
Create a Major
所以,通过@Scope实现了局部的单例。
dagger2原理分析
1.MakeMajorModule
和MakeMajorModule_ProvideMajorAFactory
自己写的类 @Module public class MakeMajorModule { public MakeMajorModule() { } // @QualifierA @StudentScope @Provides public Major provideMajorA(){ return new Major("kanghanbin"); } } Dagger2生成的工厂类 public final class MakeMajorModule_ProvideMajorAFactory implements Factory<Major> { private final MakeMajorModule module; public MakeMajorModule_ProvideMajorAFactory(MakeMajorModule module) { assert module != null; this.module = module; } @Override public Major get() { return Preconditions.checkNotNull( module.provideMajorA(), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<Major> create(MakeMajorModule module) { return new MakeMajorModule_ProvideMajorAFactory(module); }
可以看到dagger2依据我们写的类给我们提供了一个工厂类,get()方法调用了MakeMajorModule
的provideMajorA
()拿到了Major,create(MakeMajorModule module)通过传进来的MakeMajorModule 创建工厂类实例。
2.StudentComponent 和DaggerStudentComponent
自己写的类 @StudentScope @Component(modules = {MakeMajorModule.class}) public interface StudentComponent { void inject(Student student); } Dagger2生成的StudentComponent的实现类 public final class DaggerStudentComponent implements StudentComponent { private Provider<Major> provideMajorAProvider; private MembersInjector<Student> studentMembersInjector; private DaggerStudentComponent(Builder builder) { assert builder != null; initialize(builder); } public static Builder builder() { return new Builder(); } public static StudentComponent create() { return builder().build(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.provideMajorAProvider = DoubleCheck.provider(MakeMajorModule_ProvideMajorAFactory.create(builder.makeMajorModule)); this.studentMembersInjector = Student_MembersInjector.create(provideMajorAProvider); } @Override public void inject(Student student) { studentMembersInjector.injectMembers(student); } public static final class Builder { private MakeMajorModule makeMajorModule; private Builder() {} public StudentComponent build() { if (makeMajorModule == null) { this.makeMajorModule = new MakeMajorModule(); } return new DaggerStudentComponent(this); } public Builder makeMajorModule(MakeMajorModule makeMajorModule) { this.makeMajorModule = Preconditions.checkNotNull(makeMajorModule); return this; } } }
DaggerStudentComponent
就是StudentComponent
的实现类,通过builder()方法返回了Builder对象,然后build创建了一个DaggerStudentComponent
对象。在构造函数中初始化了provideMajorAProvider
和 studentMembersInjector
。当调用inject时候,执行 studentMembersInjector.injectMembers(student)
。
public final class Student_MembersInjector implements MembersInjector<Student> { private final Provider<Major> majorAndMajor2Provider; public Student_MembersInjector(Provider<Major> majorAndMajor2Provider) { assert majorAndMajor2Provider != null; this.majorAndMajor2Provider = majorAndMajor2Provider; } public static MembersInjector<Student> create(Provider<Major> majorAndMajor2Provider) { return new Student_MembersInjector(majorAndMajor2Provider); } @Override public void injectMembers(Student instance) { if (instance == null) { throw new NullPointerException("Cannot inject members into a null reference"); } instance.major = majorAndMajor2Provider.get(); instance.major2 = majorAndMajor2Provider.get(); } public static void injectMajor(Student instance, Provider<Major> majorProvider) { instance.major = majorProvider.get(); } public static void injectMajor2(Student instance, Provider<Major> major2Provider) { instance.major2 = major2Provider.get(); } }
create方法在DaggerStudentComponent
中被调用用来创建一个Student_MembersInjector
对象,injectMembers
()方法也是在DaggerStudentComponent
的inject中被调用初始化,用majorAndMajor2Provider.get()来初始化Student中的两个Major对象,从而Student依赖需求方就得到了major和major2的实例。而这里的majorAndMajor2Provider.get()就是MakeMajorModule_ProvideMajorAFactory
里面的get方法。
换一种角度去思考(结合方法调用顺序再来一波分析)
首先Major构造方法是在provideMajor
被调用的,然后看看是谁调用了provideMajor
这个方法,发现是被MakeMajorModule_ProvideMajorFactory
类里的get方法调用,再看看是谁调用了get方法,看到了是由Student_MembersInjector
的injectMembers
方法调用,而injectMembers
正是在DaggerStudentComponent
的inject方法中执行的,瞬间恍然大悟,从后往前看更容易理解
@Provides
public Major provideMajor(){
return new Major("kanghanbin");
}
@Override
public Major get() {
return Preconditions.checkNotNull(
module.provideMajor(), "Cannot return null from a non-@Nullable @Provides method");
}
@Override
public void injectMembers(Student instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
//都调用的是
instance.major = majorAndMajor2Provider.get();
instance.major2 = majorAndMajor2Provider.get();
}
@Override
public void inject(Student student) {
studentMembersInjector.injectMembers(student);
}
总结
本文只是简单的对dagger2分析了一下,没有真正在安卓项目中去运用,在开发安卓App过程中会遇到的比这更复杂,但是相信掌握了本篇所讲内容,再去结合实际开发去用它就没有那么难了。