Dagger2基础内容归纳

摘要:最近看了很多讲 Dagger2 的文章,发现Dagger中很多基础的概念问题真的挺缥缈,很难理解透彻,其中有几篇确实写得不错,这里将他们总结一下,底部给上参考文章链接。

@Inject

@Inject 注解只是 JSR-330 中定义的注解,这个注解本身是没有意义的,它需要依赖于注入框架才有意义,用于标记需要被注入框架注入的方法,属性,构造方法,也就是说呗 @Inject 标记的就是用于提供依赖的.

@Inject 定义

1
2
3
public @interface Inject {
}

@Inject 的使用

  1. 构造方法注入

@inject 注解在在构造器注入上又有两层意思

  • 告诉 Dragger2 可以使用这个构造方法构建对象用于提供依赖

  • 注入构造方法所需要的参数的依赖

  1. 属性注入

用于标注在属性上,被标注的属性不能用 private 修饰,否则无法注入

  1. 方法注入

标注在 public 方法上,Dagger2 会在构造方法执行结束之后,立刻调用被 @inject 标注的方法.

方法注入和属性注入没有本质的区别,那么什么时候用该使用方法注入,什么时候应该使用属性注入呢,比如依赖需要使用 this 对象的时候,就适合使用方法注入,因为方法注入是在构造方法执行结束之后就调用的,所以它可以提供安全的 this 对象。

Inject 的 弊端

  1. 假设我们现在依赖了第三方的框架,这个第三方的框架我们是不能修改的,所以我们无法注入.

  2. 如果某个用于提供依赖的类具有多个构造方法,我们只能标注一个,无法标注多个

  3. 当我们使用依赖倒置原则的时候,因为需要注入的对象是抽象的,因此也如法注入

@Component

Component 负责将 被依赖对象 给注入到 需要依赖对象 当中,类似于一个中间层.

Component 定义

Dagger2 是使用 @Component 来完成依赖注入的, 定义如下:

1
2
3
4
5
public @interface Component {
Class<?>[] modules() default {};
Class<?>[] dependencies() default {};
}

需要注意几点:

  1. Component 需要用接口来定义,
  2. 接口命名方式推荐为: *TargetClassName*Component
  3. 在编译之后,Dagger2 会生成 Dagger*TargetClassName*Component ,这是 *TargetClassName*Component 接口的实现
  4. TargetClass 中使用 DaggerTargetClassNameComponent 就可以实现依赖注入

@Component 中定义方法的方式

1. void inject(TargetClassName calss)

Dagger2 会从 TargetClass 开始查找 @Inject 注解,自动生成依赖注入的代码,调用 inject 即可完成依赖的注入

2. TargetClass getTargetClass()

Dagger2 会到 TargetClass 中寻找被@Inject 注解的构造方法,自动生成提供 TargetClass 依赖的代码,这种方式一般用于为其他的 Component 提供依赖,即一个 Component 作为另外一个 Component 的依赖

3. 使用 @SubComponent 的方式

1
2
3
4
5
6
7
8
9
@Component
interface AComponpent {
XxxComponent plus(Module... modules)
}
@Subcomponent(modules = xxxxx)
interface XxxComponent {
}
  • xxxComponent 是该 AComponpent 的依赖,被 @Subcomponent 标注。
  • modules 参数则是 xxxComponent 指定的 Module
  • 在重新编译后,Dagger2 生成的代码中,Subcomponent 标记的类是 Componpent 的内部类。

总结一下 目前为止(后面还有Module注解) Component 的作用:

Dagger2Component 中定义的方法作为入口,到 TargetClass 中去寻找被 @Inject 标注的属性,查找到这个属性之后,就会去接着查找该属性对应的 用 @Inject 标注的构造函数,剩下的工作就是初始化该属性的实例,并且将实例赋值给属性.这是通过生成一系列提供依赖的 Factory 类和注入依赖的 Injector 类,来实现的.

Component 和 Inject 的关系小结

Component

  1. @Inject 标注目标类中的其他类
  2. @Inject 标注其他类中的构造方法
  3. 若其他类中还依赖于别的类,那么重复上述两个步骤
  4. 调用 ComponentinjectXXX 方法,Component 会把目标类依赖的实例给注入到目标类当中,用于初始化目标类当中的依赖。

@Module

Module 定义

1
2
3
4
public @interface Module {
Class<?>[] includes() default {};
}

@Module 引入原因

如果我们项目当中使用了第三方的框架,那么可能某个 TargetClass 就持有对框架中某个类 C 实例的引用,那么按照上面两个注解的方式,我们就需要到框架当中 C 类的构造方法上面去标注一个 @Inject,况且不说构造方法多样性的问题(比如 Universal-Image-LoaderImageLoader 类构造方法二三十个),框架当中的源码我们是不可以修改的啊。这个时候 @Inject 就失效了,我们就需要一个新的工具去注解,这时就引入了 @Module 了。

1
2
3
4
5
6
7
8
@Module
public class ModuleClass{
//A是第三方类库中的一个类
A provideA(){
return A();
}
}

Module 是一个简单工厂模式,Module 里面的方法基本上都是创建类实例的方法,那么此时如何让 ComponentModule 产生联系呢?

Component 的新职责

Component 是注入器,它一端连接 TargetClass,另一端连接 TargetClass 依赖的实例,它把 TargetClass 依赖实例注入到 TargetClass。上文中的 Module 是一个提供类实例的类,所以 Module 应该是属于 Component 的实例端的(连接各种目标类依赖实例的端),Component新职责就是管理好 ModuleComponent 中的 modules 属性可以把 Module 加入 Componentmodules 可以加入多个 Module

那么接下来的问题就是,如何将 Module 中各种提供实例的方法同 TargetClassInject 标注的类属性给链接起来,这个时候 Provides 就可以出来了。

##@Provides##

###Provides 定义###

1
2
3
public @interface Provides {
}

Module 中创建实例的方法是用 Provides 标注的,之前说过,Component 搜索到 TargetClass 中用 @Inject 标注的属性之后,他就会去这个属性的类中寻找标注了 @Inject 的构造方法 ,其实在这个步骤之前,它优先去 @Module 标注的类中查找 @Provides 标注的用于创建实例的方法,如果没有找到,那么才会去查找标注了 @Inject 的构造方法。

这么一来,第三方类库的依赖注入问题就解决了。

上述注解小结

1.Inject 是用于标注 TargetClass 中的依赖和依赖类中的构造函数的。

2.Component 是一个注入器(Injector) ,同时也起着桥梁的作用, 一端是类创建实例端(即负责创建生产类的实例),另外一端是 TargetClass(即需要进行依赖初始化的类),同时也负责管理 Module

3.ModuleProvides 是为觉得第三方库注入问题而引出的,Module 是一个简单工厂模式,Module 包含创建实例的方法,这个方法用 Provides 来标注。

4.创建依赖类有两个途径:通过 @Inject 标注的构造方法来创建; 通过 @Provides 标注的创建实例的方法来创建; 但是后者的优先级要大于前者,也就是说,Component 如果找到了后者,他就不会再接着去找前者。

5.@Module 要和 @Provides 配套使用,并且 @Component 也指定了该 Module 的时候,才能正常使用,@Module 告诉 @Component ,你可以从我这儿标注了 @Privides 的方法中获取实例。

6.ComponentModule 是匹配关系 , Component 依赖哪一个 Module 就需要在注解中用 muduls 属性标明。

@Inject @Component @Module @Provides 就是 Dagger2 框架中最核心的部分,奠定了整个框架的基础,下面的标签就是针对细节问题的处理。

@Qualifier

问题的引出

现在有种情况,用上述注解无法解决:

根据依赖倒置原则,我们应该面向接口编程,或者是面向抽象编程,在 Java 中多态的性质很好的支持了这一原则,所以我们经常会在类中申明的是某一属性的接口,或者是抽象类,这样操作在程序编译的时刻,是不能确定这个属性的具体实例是哪一个子类,只能在运行时才能确定下来,那么这个时候 Component 怎么知道应该将哪一个子类给注入到 TargetClass 的属性当中 ?

基于上面两个问题,就提出了 @Qualifier 注解,它就是用于解决上述问题的。

这个时候就需要给各个 抽象类或者接口 的子类的构造方法标注 @Qualifier,类似于给他们一个 ID ,通过这个 ID 就可以区分不同的子类。

Qualifier 的定义

1
2
3
public @interface Qualifier {
}

这个注解跟 @Inject 一样,不是 Dagger 定义的, 而是 JSR-330 中定义的。

  • Qualifier 是用于定义注解的。

Qualifier 使用方法

  1. 使用 @Qualifier 根据子类的不同,分别定义新的注解,注解要有含义
  2. 分别使用新的注解去标注生成不同子类实例的地方,然后要使用哪一个子类的实例,是到抽象属性上标注子类对应的新的注解。

@Scope 和 @Single

Scole 的定义

1
2
3
public @interface Scope {
}
  • 也是 JSR-330 定义的,不是 Dagger 中定义的
  • 用于自定义注解
  • @Single@Scope 的默认实现,如下:
1
2
3
4
5
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

Scope 的作用

  • 它的作用只是保证依赖在 @Component 中是唯一的,可以理解为“局部单例”。

  • @Scope 是需要成对存在的,在 ModuleProvide 方法中使用了 @Scope,那么对应的 Component 中也必须使用 @Scope 注解,当两边的 @Scope 名字一样时(比如同为 @Singleton), 那么该 Provide 方法提供的依赖将会在 Component 中保持“局部单例”。

  • 而在 Component 中标注 @Scopeprovide 方法没有标注,那么这个 Scope 就不会起作用,而 Component 上的 Scope 的作用也只是为了能顺利通过编译。

这么说 @Single 是没有创建单例的能力,因为他只是保证在 Component 中的唯一的,那怎么实现真正的单例呢?

单例的实现

  1. 依赖在 Component 中是单例的(供该依赖的 provide 方法和对应的 Component 类使用同一个 Scope 注解。)
  2. 对应的 ComponentApp 中只初始化一次,每次注入依赖都使用这个 Component 对象。(在 Application 中创建该 Component

Lazy

这个比较简单,延迟加载模式,用 Lazy<T> 装饰需要被 @Inject 标注的属性 T ,这样,在 Inject 的时候并不会初始化它,而是在使用 T 的时候,通过 T.gey() 来得到他的实例,然后再使用。

Component 组织方式

这是重中之重,前面的概念都是做铺垫,这里从一个 APP 的角度将他们融合起来。

一个app中应该根据什么来划分Component

假如一个 appapp 指的是 Android app)中只有一个 Component,那这个 Component 是很难维护、并且变化率是很高,很庞大的,就是因为 Component 的职责太多了导致的。所以就有必要把这个庞大的 Component 进行划分,划分为粒度小的 Component。那划分的规则这样的:

  1. 要有一个全局的 Component (可以叫 ApplicationComponent ),负责管理整个 app 的全局类实例(全局类实例整个 app 都要用到的类的实例,这些类基本都是单例的)

  2. 每个页面对应一个 Component,比如一个 Activity 页面定义一个 Component,一个 Fragment 定义一个 Component。当然这不是必须的,有些页面之间的依赖的类是一样的,可以公用一个 Component

第一个规则应该很好理解,具体说下第二个规则,为什么以页面为粒度来划分 Component

  1. 一个 app 是由很多个页面组成的,从组成 app 的角度来看一个页面就是一个完整的最小粒度了。
  1. 一个页面的实现其实是要依赖各种类的,可以理解成一个页面把各种依赖的类组织起来共同实现一个大的功能,每个页面都组织着自己的需要依赖的类,一个页面就是一堆类的组织者。
  1. 划分粒度不能太小了。假如使用 mvp 架构搭建 app,划分粒度是基于每个页面的m 、v 、p 各自定义 Component 的,那 Component 的粒度就太小了,定义这么多的 Component,管理、维护就很非常困难。

所以以页面划分 Component 在管理、维护上面相对来说更合理。

组织Component

我们已经把一个 app 按照上面的规则划分为不同的 Component 了,全局类实例也创建了单例模式。问题来了:其他的 Component 想要把全局的类实例注入到目标类中该怎么办呢?
这就涉及到类实例共享的问题了,因为 Component 有管理创建类实例的能力。因此只要能很好的组织 Component 之间的关系,问题就好办了。具体的组织方式分为以下2种:

  1. 依赖方式
    一个 Component 是依赖于一个或多个 ComponentComponent 中的 dependencies 属性就是依赖方式的具体实现

  2. 包含方式
    一个 Component 是包含一个或多个 Component 的,被包含的 Component 还可以继续包含其他的 Component。这种方式特别像 ActivityFragment 的关系。SubComponent 就是包含方式的具体实现。

Dagger 注入一次的流程

步骤1:查找 Module 中是否存在创建该类的方法。

步骤2:若存在创建类方法,查看该方法是否存在参数

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

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

步骤3:若不存在创建类方法,则查找 Inject 注解的构造函数,看构造函数是否存在参数

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

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

总结对象

  1. Android:dagger2让你爱不释手-基础依赖注入框架篇
  2. Android:dagger2让你爱不释手-重点概念讲解、融合篇
  3. Android:dagger2让你爱不释手-终结篇
共82.3k字
0%
.gt-container a{border-bottom: none;}