Dagger2解析-1

如果还没入门的,可以先去搜搜基本用法,本系列主要偏向原理

Dagger版本 2.11

1.Dagger2

Dagger2是啥,Google告诉我们:

Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier versioncreated by Square and now maintained by Google.
Dagger aims to address many of the development and performance issues that have plagued reflection-based solutions. More details can be found in this talk(slides) by +Gregory Kick.

Dagger是为Android和Java平台提供的一个完全静态的,在编译时进行依赖注入的框架,原来是由Square公司维护的然后现在把这堆东西扔给Google维护了。Dagger解决了基于反射带来的开发和性能上的问题(因为Dagger并没有用反射来做依赖注入, 靠的是注解生成代码实现依赖注入)

ps1:本文不讲使用方法
ps2:本篇实例使用的都是kotlin

2.简单用法

举个例子,Target类里有一个Member类的成员变量,也就是Target依赖Member

class Member constructor()
class Target(val member: Member)

看上去并没有什么问题,但是这并不是好的写法.
如果Target类不单单依赖Member,还依赖很多类的话,就得写很多创建实例的代码,甚至如果以后Member类需要修改代码时,那还得修改Target类的代码.如果Member类被很多类依赖的话,修改起来就更加困难.

所以dagger2就是用来完成这个实例化并赋值b = new Member();的事情(也就是依赖注入)

dagger的注解有component\inject\module\scpoe

首先来最简单的写法,Member构造方法没有参数,Target依赖Member的情况,也就是上面例子那样的

给Member的构造函数添加@Inject注解,告诉dagger此类是可以实例化的

class Member @Inject constructor()

接着定义一个Component,也就是注入器(dagger就是根据这个接口生成实际的注入代码)

@Component
interface TargetComponent {
  fun inject(target:Target)
}

然后是Target,member标记@Inject表示该变量可以被注入(从上面生成注入器的操作也能看出注入的代码不在类本身里面,所以member变量是不能标记为final或者私有的)

class Target {
  @Inject
  lateinit var member:Member
}

这样标注的过程就完成了,编译之后,就会生成DaggerTargetComponent(名字为Dagger+你定义的Component接口)的名称的类,在需要注入的时候调用

val target = Target()
DaggerTestComponent.builder().build().inject(target)

这样target里面的member实例就被注入进去了

3.dagger生成的代码解析

下面我们来看dagger生成的代码,找到build/generated/kapt/debug/xxx(你的包名)


如果是java就不是kapt了,但生成代码的地方都是在generated里面的

可以看到生成了三个类,

  • DaggerTargetComponent:表示这层依赖关系的组件
  • Member_Factory:提供依赖实例的工厂
  • Target_MembersInjector:就是实际用于注入依赖的注入器

下面再来看这三个类

3.1 DaggerTargetComponent

DaggerTargetComponent

可以看到这是一个Builder模式(这里看上去简单,但依赖多了就复杂了)
这里主要看inject方法,用注入器调用injectMembers方法,传入的参数是我们需要注入Member的Target实例
跟进injectMembers(target)方法


这就是一个dagger定义的接口,那么就得看具体实现了,回到DaggerTargetComponent, 找到initialize方法,这里是注入器targetMembersInjector初始化的地方。
this.targetMembersInjector = Target_MembersInjector.create(Member_Factory.create());
先跟进Member_Factory.create()方法,来到代码生成的第二个类Member_Factory

3.2 Member_Factory

Member_Factory

这就是一个简单的工厂类,用来提供Member实例的,我们知道Member的构造方法被标记@Inject了,加上又没有参数,所以生成的代码就直接new了
其他没啥好讲了,回到前面的
this.targetMembersInjector = Target_MembersInjector.create(Member_Factory.create());
接下来就是跟进Target_MembersInjector.create()方法了

3.3 Target_MembersInjector

直接注入依赖的地方

这里主要就是injectMembers的实现了
instance.member = memberProvider.get();
memberProvider就是前面的工厂(Member_Factory实现了Factory<Member>,而Factory<Member>继承了Provider<Member>),提供了Member的实例
这里就能看出为啥Target的成员member不能是final(赋值在类外)和至少得是包级的权限(同包不同类)

ps:关于生成代码的包位置,基本上就是对应的类生成的包位置相同

  • TargetComponent->DaggerTargetComponent
  • Target->Target_MembersInjector
  • Member->Member_Factory
挪一下位置,生成的位置就不同了

4.依赖不能直接创建的情况

前面的例子中,Member的构造函数是无参的,那么如果有参数呢?
这里又分为两种情况:

  • 参数对象无法直接创建
  • 参数对象可以直接创建

4.1参数对象无法直接创建

这里就是另外一个注解Module以及Provide的作用了,顾名思义,这Module就是Target所依赖的一个模块提供者.

改写一下Member,构造函数需要一个String类型的参数
class Member @Inject constructor(val name: String)

然后创建一个MemberModule类

@Module
class MemberModule {
    @Provides
    fun provideMember(): Member = Member("from module provider")
}

Module表示这是模块,可以放在组件中。
Provides表示这是实例提供者。
这样Dagger注入时就可以在模块中寻找依赖对象的实例化方法

接着是Component

@Component(modules = arrayOf(MemberModule::class))
interface TargetComponent {
    fun inject(target: Target)
}

Component多了一个modules参数,代表这个组件所包含的模块,一个组件可以包含多个模块,所以是参数是Module数组

这样就改造完成了,编译后,注入的代码和原来一样

DaggerTargetComponent.builder()
            .build()
            .inject(target)

看看生成的代码


为了方便看,又把几个类放在一个包里了

可以看到,比之前多了一个MemberModule_ProvideMemberFactory

看回DaggerTargetComponent,这里比之前就多了一些代码


3处变化
  1. builder多了一个参数,就是MemberModule
  2. 这个MemberModule可以不传,因为MemberModule是构造函数是无参的,能直接new
  3. 工厂不一样了,create的传参也不一样了

接下来看看这个工厂


这个工厂在其他情况下才会用到,本例里是没用的

对比一下可以发现,其实就是再把Member的依赖name外包出去给别的提供者提供实例,这种情况后面再讲

新的工厂

代码很简单,从原来直接new变成了MemberModule的provideMember()方法产生实例而已

4.1.1Module不直接提供Member实例

上文提到一个没用的工厂,那么什么时候会产生作用呢?
改造一下Module,这次Provide不直接提供Member实例,返回一个String即可(Member的构成函数就一个String类型的参数)

@Module
class MemberModule {

    @Provides
    fun provideMemberName(): String = "from module String provider"

//    @Provides
//    fun provideMember(): Member = Member("from module Module provider")
}

编译,看生成的代码

工厂名字不一样了

可以看到这个工厂是提供String类型的

再来看看DaggerTargetComponent
上文没用的工厂类用上了

熟悉的配方,熟悉的味道

还是一样的直接new,只不过参数name的实例化由MemberModule_ProvideMemberNameFactory提供

4.2参数对象可以直接创建

好,之前的Member的构造参数是一个String,这个Dagger可没法创建,那么如果是一个可创建的呢?例如Member又依赖一个MemberOfMember

class MemberOfMember @Inject constructor() 
class Member @Inject constructor(val memberOfMember: MemberOfMember)

把Component变回最初的样子

@Component
interface TargetComponent {
    fun inject(target: Target)
}

老样子,看生成的代码


大同小异,多了个MemberOfMember_Factory

provider的类型从String变成了MemberOfMember而已

和最开始Member无参构造函数时生成的Member_Factory一毛一样

再看看DaggerTargetComponent


嵌套工厂

看到这是不是有些明白了?这里总结一下依赖的实例化

  1. 看能不能够直接创建(带@Inject的空构造函数),能就直接new
  2. 不能直接创建的,看module中有没有提供直接实例化的方法
  3. 没有提供直接实例化方法的,看有没有能够满足依赖实例化所需所有参数的实例化方法(这里会是一个递归的过程,参数的实例化会重复123的步骤)

ps:所有可以被自动实例化(1,3的情况)的对象构造参数都必须带@Inject
ps:2 @Inject声明属性表示这个属性可以被注入,属性本身不能是final,也不能在包级别以下的权限
@Inject不是万能的,例如接口没法实例化,或者是第三方的代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容