美文网首页
Dagger2使用

Dagger2使用

作者: 阿里小鸟 | 来源:发表于2020-11-22 17:26 被阅读0次

    为什么使用Dagger2

    无论是构造函数注入还是接口注入,都避免不了要编写大量的模板代码。机智的猿猿们当然不开心做这些重复性的工作,于是各种依赖注入框架应用而生。但是这么多的依赖注入框架为什么我们却偏爱Dagger2呢?我们先从Spring中的控制反转(IOC)说起。

    谈起依赖注入,做过J2EE开发的同学一定会想起Spring IOC,那通过迷之XML来配置依赖的方式真的很让人讨厌;而且XML与Java代码分离也导致代码链难以追踪。之后更加先进的Guice(Android端也有个RoboGuice)出现了,我们不再需要通过XML来配置依赖,但其运行时实现注入的方式让我们在追踪和定位错误的时候却又万分痛苦。开篇提到过Dagger就是受Guice的启发而开发出来的;Dagger继承了前辈的思想,在性能又碾压了它的前辈Guice,可谓是长江后浪推前浪,前浪死在沙滩上。

    又如开篇我在简介中说到的,Dagger是一种半静态半运行时的DI框架,虽说依赖注入是完全静态的,但是生成有向无环图(DAG)还是基于反射来实现,这无论在大型的服务端应用还是在Android应用上都不是最优方案。升级版的Dagger2解决了这一问题,从半静态变为完全静态,从Map式的API变成申明式API(@Module),生成的代码更优雅高效;而且一旦出错我们在编译期间就能发现。所以Dagger2对开发者的更加友好了,当然Dagger2也因此丧失了一些灵活性,但总体来说利还是远远大于弊的。

    前面提到这种A B C D E连续依赖的问题,一旦E的创建方式发生了改变就会引发连锁反应,可能会导致A B C D都需要做针对性的修改;但是骚年,你以为为这仅仅是工作量的问题吗?更可怕的是我们创建A时需要按顺序先创建E D C B四个对象,而且必须保证顺序上是正确的。Dagger2就很好的解决了这一问题(不只是Dagger2,在其他DI框架中开发者同样不需要关注这些问题)。

    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标注的接口为CarComponent,则编译期生成的实现类为DaggerCarComponent),我们通过调用这个实现类的方法完成注入;

    @Qulifier:@Qulifier用于自定义注解,也就是说@Qulifier就如同Java提供的几种基本元注解一样用来标记注解类。我们在使用@Module来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以provide开头,但这并不是强制的,只是为了增加可读性而已)。那么Dagger2怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2根据返回值的类型来决定为哪个被@Inject标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型Dagger2就懵逼了。@Qulifier的存在正式为了解决这个问题,我们使用@Qulifier来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject标注的变量),这样Dagger2就知道为谁提供依赖了。----一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;

    @Scope:@Scope同样用于自定义注解,我能可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;

    @Singleton:@Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的Component是否为一个全局对象。

    我们提到@Inject和@Module都可以提供依赖,那如果我们即在构造函数上通过标记@Inject提供依赖,有通过@Module提供依赖Dagger2会如何选择呢?具体规则如下:

    步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。

    步骤2:若存在提供依赖的方法,查看该方法是否存在参数。

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

    b:若不存在,则直接初始化该类实例,完成一次依赖注入。

    步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。

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

    b:若不存在,则直接初始化该类实例,完成一次依赖注入。

    Dagger2注解@Module ,@Component,@Inject的关系

    简单的说,就是一个工厂模式,由Dagger负责创建工厂,帮忙生产instance。遵从Java规范JSR 330,可以使用这些注解。现在不研究Dagger2是如何根据注解去生成工厂的,先来看看工厂是什么东西,理解为什么可以实现了DI(Dependency Injection),如何创建IoC(Inverse of Control)容器。

    Dagger2是通过依赖注入完成类的初始化。

    这个过程需要三部分:

    #1****依赖提供方(生产者)

    #2****依赖注入容器(桥梁)

    #3****依赖需求方(消费者)

    image

    总结:

    @Inject主要有两个作用

    #1作为依赖注提供方

    使用@Inject注解构造方法。

    注解构造函数,让Dagger2帮我们实例化该,并注入。

    #2作为依赖需求方:

    使用@Inject注解成员。

    如果一个成员变量被@Inject注解修饰,并且成员类构造函数也被@Inject注解,那么dagger2帮我们实例化该成员类,并注入。

    通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。

    使用@Inject可以让IoC容器负责生成instance,如果没有这个注解,dagger将不认识,当做普通类,无法代理

    @Module的作用

    #1@Module注解,负责管理依赖。

    Module 其实是一个简单工厂模式,Module 里面的方法都是创建相应类实例的方法。

    #2通过@Module获得第三方类库的对象。

    #3@Module是一个依赖提供方的合集。

    @ModulepublicclassAModule{@ProvidespublicGsonprovideGson(){returnnewGson();}}

    @Provides

    #1注解@Module中的方法

    在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。

    @Component的作用

    #1@Component一般用来注解接口

    #2负责在@Inject和@Module之间建立连接。

    也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。

    #3实例化@Inject注解的时,遇到没有构造函数的类依赖,则该依赖由@Module修饰的类提供。

    #4****依赖注入容器只是一个接口interface。

    Component需要引用到目标类的实例,Component会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字注入器(Injector)

    Component注解的类,再编译之后,会生产一个以Dagger+类名的一个类,如下面的MainComponent会生成类DaggerMainComponent(补充一点,Kotlinkapt编译生成类的位置:\build\generated\source\kapt\debug),我们需要在目标类MainActivity中加入下面代码

    DaggerMainComponent.builder()

                .build()
    
                .inject(this)
    

    DaggerMainComponent使用了建造者设计模式,inject方法是我们MainComponent中定义的,这样目标类就和Component建立了联系.Component会去遍历使用@Inject注解的常量,然后去查找对应的类是否有@Inject注解的构造方法,如果没有就会报异常.

    @Component{modules={HeaterModule.class,PumperModule.class}}publicinterfaceMachineComponent{voidinject(CoffeeMachine machine);}

    dagger中Component就是最顶级的入口,dagger为之生成了工厂类 DaggerMachineComponent,目标是构建CoffeeMachine, 在CoffeeMachine中使用了Injection,那么依赖要由工厂类来提供。工厂类是根据modules的参数来找依赖绑定的。

    相关文章

      网友评论

          本文标题:Dagger2使用

          本文链接:https://www.haomeiwen.com/subject/yxouiktx.html