轻松学,听说你还没有搞懂 Dagger2

Posted frank909

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了轻松学,听说你还没有搞懂 Dagger2相关的知识,希望对你有一定的参考价值。

Dagger2 确实比较难学,我想每个开发者学习的时候总是经历了一番痛苦的挣扎过程,于是就有了所谓的从入门到放弃之类的玩笑,当然不排除基础好的同学能够一眼看穿。本文的目的尝试用比较容易理解的角度去解释 Dagger2 这样东西。

Dagger2 是有门槛的,这样不同水平能力的开发者去学习这一块的时候,感受到的压力是不一样的。

我个人总结了大家在学习 Dagger2 时,为什么感觉难于理解的一些原因。

  1. 对于 Java 注解内容不熟悉。
  2. 对于依赖注入手段不熟悉。
  3. 对于 Java 反射不熟悉。
  4. 对于 Dagger2 与其它开源库的使用方法的不同之处,没有一个感性的认知。
  5. 对于 Dagger2 中极个别的概念理解不够。
  6. 对于 Dagger2 的用途与意义心生迷惑。

其实以上几点,都可以归类到基础技能不扎实这个范畴内,但正如我所说的,学习 Dagger2 时开发者的水平是不一样的,所以困扰他们的原因就不一样。下面,我针对这些情况,一一给出自己的建议。

对于 Java 注解不熟悉

这一部分的开发者基础知识确实薄弱,那么怎么办呢?当然是学习了。就算不为 Dagger2,注解的知识内容也应该好好值得学习,虽然在平常开发中,我们自己编写注解的机会很少,但是我们运用第三方开源库的时候,应该会经常看见注解的身影,所以熟悉注解不是为了自己编写注解代码,而是为了开发过程中更加高效从容而已。

如果,对 Java 注解一无所知,我可以给大家一个感性的认知。

一般,我们评价某人会说,这是一个好人、坏人、男神、女神、大神、单身狗等等,这是我们人为贴得标签,这些标签有助于我们自己或者其他人去获取被评价的人的基本信息。

而在 Java 软件开发中,我们也可以给某些类,某些字段贴上作用类似的标签,这种标签的名字就叫做注解,只不过这种标签是给代码看的。

标签只对特定的人起作用,比如小张被人贴了一个小气鬼的标签,所以小红认为小张是一个小气鬼,但是小张本人不会因为这个标签而改变自己变得不是小张,也许本质上小张是个大方的人。

所以,注解本身也不会影响代码本身的运行,它只会针对特定的代码起到一定的用处,用来处理注解的代码被称作 APT(Annotation Processing Tool)。

更详细的内容请阅读这篇文章《秒懂,Java 注解 (Annotation)你可以这样学》

对依赖注入手段不熟悉

这一块而言,如果让很多人慌张的原因,我觉得可能是依赖注入这个词过于学术化了。而从小到大,10 多年的应试教育让绝大部分的同学对于这些枯燥无味的概念产生了恐惧与绝望。其实,没有那么夸张的,不要被这些东西吓倒。

因为,Java 学习的时候,我们一直写这样的代码。

class B


class A 
    B b;

    public A() 
        b = new B();
    

这样的代码,一点问题都没有,类 A 中有一个成员变量 b,b 的类型是类 B。所以,在软件开发中,可以称 A 依赖 B,B 是 A 的依赖,显然,A 可以依赖很多东西,B 也可以依赖很多东西。

通俗地讲,依赖这个概念也没有什么神奇的,只是描述了一种需求关系。

我们再来看一种情况,现在,业务需要,代码越来越复杂。

class B

class C
    int d;
    public C (int value) 
        this.d = value;
    



class A 
    B b;
    C c;

    public A() 
        b = new B();
        c = new C(3);
    

现在,A 有了一个新的依赖 C。不过,由于业务的演进,C 这个类经常发生变化,最明显的变化就是它的构造方法经常变动。

class C
    int d;
    String e;
    public C (String value) 
        this.e = value;
    



class A 
    B b;
    C c;

    public A() 
        b = new B();
        //c = new C(3);
        c = new C("hello");
    

C 变动的时候,由于 A 依赖于它,A 不得不修改自己的代码。但是,事情还没有完。C 还会变动,C 把 B 也带坏了节奏。

class B
    int value;

    public B(int value) 
        this.value = value;
    



class C
    int d;
    String e;
    public C (int index,String value) 
        this.d = index;
        this.e = value;
    



class A 
    B b;
    C c;

    public A() 
        b = new B(110);
//      b = new B();
        //c = new C(3);
//      c = new C("hello");
        c = new C(12,"hello");
    

可以想像的是,只要 B 或者 C 变动一次,A 就可能需要修改自己的代码,用专业术语描绘就是A 与依赖模块太过于耦合,这个可是犯了软件设计的大罪,

我们再可以想像一下,A 是领导,B 和 C 是小兵,如果因为 B 和 C 自身的原因,导致领导 A 一次次地改变自己,那么以现在流行的话来说就是,“你良心不会痛吗?”。所以我们需要的就是进行一些变化来进行解耦,也就是解除这种耦合的关系。让 A 不再关心 B 和 C 的变化,而只要关心自身就好了。

class A 
    B b;
    C c;

    public A(B b, C c) 
        this.b = b;
        this.c = c;
    

  

在上面代码中,A 不再直接创建 B 与 C,它把依赖的实例的权力移交到了外部,所以无论 B 和 C 怎么变化,都不再影响 A 了。这种实例化依赖的权力移交模式被称为控制反转(IoC),而这种通过将依赖从构造方法中传入的手段就是被传的神乎其乎的依赖注入(DI)。其实,本质上也没有什么神奇的地方,只是起了一个高大上的名字而已,好比东北的马丽,在国际化上的大舞台,宣称自己是来自神秘东方的 Marry 一样。

依赖注入有 3 种表现形式。
构造方法注入

class A 
    B b;
    C c;

    public A(B b, C c) 
        this.b = b;
        this.c = c;
    

 

Setter 注入

class A 
    B b;
    C c;


    public void setB(B b) 
        this.b = b;
    


    public void setC(C c) 
        this.c = c;
    


接口注入

interface Setter 
    void setB(B b);
    void setC(C c);



class A implements Setter
    B b;
    C c;

    public void setB(B b) 
        this.b = b;
    


    public void setC(C c) 
        this.c = c;
    


大家肯定会想,依赖注入的引进,使得需求方不需要实例化依赖,但总得有地方去实例化这些依赖啊。确实,依赖注入引进了第三方,你可以称它为 IoC 容器,也可以称它为注射器(injector),为了便于理解,我们之后都有注射器来指代吧,通过注射器可以将依赖以上面 3 种注入方式之一注入到需求方。

病人需要的是药水,所以病人是需求者,药水是病人的依赖,注射器把药水注射给病人。

更多细节,请阅读《轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI)》

而 Dagger2 就是一个依赖注入框架,你也可以想像它是一位非常智能化的服务员,用来处理大量的顾客的各种订餐需求,然后针对不同的菜单提供给不同的顾客不同类型的餐具。

对于 Java 反射不熟悉

对于这一块不熟悉的同学同样是基础知识太薄弱,需要补强。

相对于正常流程开发,Java 反射是非常规化手段。如果正常流程开发是司机驾驶一辆汽车,那么反射的运用就是采用无人驾驶的手段。

Dagger2 中也应用了反射,不过开发者本身不需要运用反射,Dagger2 是自身框架通过反射处理注解。

学习反射内容可以阅读这篇文章《细说反射,Java 和 Android 开发者必须跨越的坎》

Dagger2 与其它开源库略有不同

开源软件的出现,大大造福了程序员,所以,大家都说不要重复创造轮子

但是,我个人一直认为,不重复创造轮子,不代表可以不去深入了解这些轮子。

我把 android 开发中所应用到的开源库当作武装

武装与两部分构成,武器装备

那么,在 Android 中什么样的库可以当作是武器呢?什么样的库可以当作是装备呢?

大家想一下,武器什么用途?战斗进行中,用来杀敌的

装备呢?战斗开始时,就要穿上或者安装好的物件。

刀、枪、棍、棒是武器,盔甲是装备。
武器拿来就用,盔甲等却要在开始战斗前就装备上。

Java 软件代码是在虚拟机中运行的,所以在这里可以把 jvm 当作战场。

Piccso、Logger、sweet-alert-dialog 等等,这些开源库都是在程序运行过程中拿来就用的。

而 GreenDao、Butterknife、Dagger2 这些因为涉及到了反射处理,而反射处理相对于正常开发速度很慢,所以它们通常在编译时产生一些新的代码,然后才能在程序运行过程中使用,也就是说它们都把反射处理移动到编译器编译代码时的阶段,而程序运行时并不涉及到反射,这就是这些框架运用了反射技术,但是仍然高效的秘诀所在。

所以,Dagger2 会产生中间代码,不少同学应该会有迷惑,为什么引进了 Dagger2 时,要先编译一次代码,不然就会报错。现在,可以解释了,编译代码是为了生成中间代码,然后在中间代码的基础上按照正常的流程开发。

Dagger2 并非横空出世

都说要站在巨人的肩膀上,Dagger2 其实也算站在巨人的肩膀上。

Dagger2 是一款依赖注入的框架,但依赖注入的框架有 ,所以 Dagger2 也并不算是一款新鲜事物,大家觉得新奇不过是因为对于依赖注入框架本身了解过少罢了。

Dagger2 是在 Dagger 的基础上来的,Dagger 是由 Square 公司开发的,Dagger2 基于 Dagger 由 Google 公司开发并维护。
Square 是一家伟大的公司,Android 大神 JakeWoton 之前就在它任职,不久前才离职。而我们熟悉的 RxJava、Butterknife、Retrofit、OKHttp 等等都是 Square 提供的,外号 Square 全家桶。

当然,Google 公司更是一家伟大的公司,这个无需多言。

最后,有个重要的地方就是 Dagger2 是基于注解开发的,而 Dagger2 中所涉及到的注解其实是基于 javax.inject 上开发的,它出自 JSR330

JSR330 是规范,建议大家怎么做,而 Dagger2 则实现了这个规范。

因此,对于普通开发者而言,学习 Dagger2 其实就是学习相关的注解的意义与用途。

Dagger2 的引进

Dagger2 是适应于 Java 和 Android 开发的依赖注入框架,记住得是它不仅仅对 Android 开发有效。

Dagger2 官网地址是 https://google.github.io/dagger//

对于 Eclipse 开发而言,需要下载相应的 jar 包。

对于 AndroidStudio 开发而言,只需要在相应的 build.gradle 引入对应的依赖就好了。

如果你 AndroidStudio 的 gradle build tool 版本在 2.2 以上,直接在引进就好了

dependencies 
  compile 'com.google.dagger:dagger:2.4'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.4'

如果你的 gradle build tool 版本在 2.2 以下,则需要引进 apt 插件。
首先需要在 Project 层级的 build.gradle 文件中引入依赖

buildscript 
    repositories 
      mavenCentral()
    
    dependencies 
        // replace with the current version of the Android plugin
        classpath 'com.android.tools.build:gradle:2.1.0'
        // the latest version of the android-apt plugin
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    

然后在 Module 层级的 build.gradle 引入相应的插件和依赖

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'


dependencies 
     apt 'com.squareup.dagger:dagger-compiler:2.4'
     compile 'com.squareup.dagger:dagger:2.4'
     //java注解
     compile 'org.glassfish:javax.annotation:10.0-b28'

Dagger2 的基本概念

前面讲到过 Dagger2 基于 JSR330 注解,在普通开发者视角中,就是这些注解构成了 Dagger2 的全部。

前面文章我提到过,注解如同标签,给一个人贴标签有助于自己去理解这个人,而给代码贴标签,有助于 APT 程序去处理相应的代码,Dagger2 有自己的注解,而这些注解也有特定的意义,它们大体上都是为了实现依赖注入。

我们说依赖注入有 3 种手段:
- 构造方法注入
- Setter 注入
- 接口注入

但是,如果不借助于框架的话,我们就必须自己编写相应的代码,这些代码充当了注射器的角色。

B b = new B(5);
C c = new C(110,"110");

A a = new A();
a.setB(b);
a.setC(c);

A 将内部的依赖 B 和 C 的实例化的权力移交到了外部,通过外部的注入。

Dagger2 这类依赖注入框架的出现进一步解放了我们的双手,Dagger2 有一套自己的依赖注入机制,我们不再手动编写注射器,而只要按照规则配置好相应的代码就好了,Dagger2 会自动帮我们生成注射器,然后在适当的时候进行依赖注入。

什么意思呢?意思就是我们不需要调用 a.setB() 和 a.setC() 方法,只需对代码添加一些注解就好了。

class B
    int value;
    @Inject
    public B(int value) 
        this.value = value;
    



class C
    int d;
    String e;
    @Inject
    public C (int index,String value) 
        this.d = index;
        this.e = value;
    


class A 
    @Inject
    B b;
    @Inject
    C c;

看起来,不可思议,不是吗?@Inject 是一个注解,只要按照 Dagger2 的配置,就能颠覆我们之前的编码习惯。

但不管看起来怎么神奇,任何事都有一个本质。

因此这个本质就是,Dagger2 是一个依赖注入框架,依赖注入的目的就是为了给需求方在合适的时候注入依赖。

对 Dagger2 学习过程如果感到不适与难以理解,回过头来想想它的本质好了。


Dagger2 的使命就是为了给需求者注射依赖。

@Inject 注解就如同一个标签,或者说它是一个记号,它是给 Dagger2 看的。它运用的地方有两处。

  1. @Inject 给一个类的相应的属性做标记时,说明了它是一个依赖需求方,需要一些依赖。

  2. @Inject 给一个类的构造方法进行注解时,表明了它能提供依赖的能力。

就这样,通过 @Inject 注解符号,就很容易标记依赖和它的需求方。但是,单单一个 @Inject 是不能让 Dagger2 正常运行的。还需要另外一个注解配合。这个注解就是 @Component。

而 @Component 相当于联系纽带,将 @inject 标记的需求方和依赖绑定起来,并建立了联系,而 Dagger2 在编译代码时会依靠这种关系来进行对应的依赖注入。

@Inject 和 @Component

我们来编写代码,验证一下。

假设有这么一个场景:

一个宅男,他喜欢在家玩游戏,所以饿了的时候,他不想自己煮饭吃,也不愿意下楼去餐厅,他选择了外卖。

public class ZhaiNan 

    @Inject
    Baozi baozi;

    @Inject
    Noodle noodle;

    @Inject
    public ZhaiNan() 

    

    public String eat() 
        StringBuilder sb = new StringBuilder();
        sb.append("我吃的是 ");
        if ( baozi != null ) 
            sb.append(baozi.toString());
        

        if (noodle != null) 
            sb.append("  ");
            sb.append(noodle.toString());
        
        return sb.toString();
    


public class Baozi 

    @Inject
    public Baozi() 
    

    @Override
    public String toString() 
        return "小笼包";
    


public class Noodle 

    @Inject
    public Noodle() 
    

    @Override
    public String toString() 
        return "面条";
    

上面代码可以看到,@Inject 注解的身影,需求方是 ZhaiNan 这个类,而 Baozi 和 Noodle 是它的依赖。前面说过,光有 @Inject 的话还不行,需要 @Component 配合。

@Component 怎么使用呢?

很简单,它只需要注解在一个接口上就好了。

@Component()
public interface Platform 
    ZhaiNan waimai();

Platform 是一个接口,它代表着外卖平台,它内部有一个 waimai() 的方法,返回 ZhaiNan 的类型。

这个接口特别的地方就是它的方法中的返回类型。如果一个方法返回了一个类型,那么其实也算是一种依赖的提供,我们可以在后续的代码中感受。

既然是接口,那么它就需要实现类,但是 Dagger2 会自动帮我们生成一个实现类,前提是使用这个类的时候,要先对工程进行编译。前面用装备解释过 Dagger2 这种类型的库,它会在编译阶段产生中间代码,这些中间代码就包括自动实现了被 @Component 注解过的接口实现类。

所以,我们如果要使用 Dagger2 为了我们自动生成的类时,我们就应该先 Build->Make Project 编译一次代码。生成的代码位置在 app 模块 build 文件夹中,在 AndroidStudio 切换 Project 视角就可以看到。

这个目录下都是 Dagger2 产生的中间产物,DaggerPlatform 就是 Dagger2 为我们自动实现的 Platform 这个接口的实现类,注意它的名字都是 Dagger+接口名称

有了 DaggerPlatform,我们就能够使用 Dagger2 进行代码的依赖注入了。

public class MainActivity extends AppCompatActivity 

    Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.btn_test);

        final ZhaiNan zainan = DaggerPlatform.builder()
                .build()
                .waimai();

        mButton.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                Toast.makeText(MainActivity.this,zainan.eat(),Toast.LENGTH_LONG).show();
            
        );

    

然后,测试效果是:

需要注意的地方是,Component 的实现类是由 Dagger2 自动生成的,它的名字前面说了是 Dagger+接口名称。但这是通常情况,因为 @Component 注解的都是顶级类。但还有一种情况是。

class Foo 
  static class Bar 
    @Component
    interface BazComponent 
  

它只是一个内部类的接口,Dagger2 针对这种情况需要把外部的类的名字加下划线的形式拼接起来,所以上例中 Dagger2 生成的 Component 实现类类名是 DaggerFoo_Bar_BazComponent。

我们并没有在任何地方用 new 关键字亲自创建 ZhaiNan 这个类的实例,但是它确实有效,而且它的内部依赖 Baozi 和 Noodle 都被实例化了,也就是说依赖被正确地注入到了 ZhaiNan 的实例对象当中。

所以,@Component 和 @Inject 的配合就能够使用 Dagger2 了,但这里面存在一个局限,@Inject 只能标记在我们自己编写的类的构造方法中,如果我们使用第三方的库或者标准库的话,是不是代表我们对于这些就无能为力了呢?

答案显然是否定的,Dagger2 作为一款优秀的框架必须考虑到开发过程中的方方面面,不然谈何优秀呢?

Dagger2 为了能够对第三方库中的类进行依赖注入,提供了 @Provides 和 @Module 两个注解。

@Provides 和 @Module

Provide 本身的字面意思就是提供,显然在 Dagger2 中它的作用就是提供依赖。
Module 是模块的意思,Dagger2 中规定,用 @Provides 注解的依赖必须存在一个用 @Module 注解的类中。

@Module
public class ShangjiaAModule 
    @Provides
     public Baozi provideBaozi() 
        return new Baozi("豆沙包");
    
    @Provides
     public Noodle provideNoodle() 
        return new Noodle();
    


public class Baozi 

    String name;

    @Inject
    public Baozi() 
        name = "小笼包";
    

    public Baozi(String name) 
        this.name = name;
    

    @Override
    public String toString() 
        return name;
    

值得注意的地方有
- @Provides 修饰的方法一般用 provide 作用方法名前缀。
- @Module 修饰的类一般用 Module 作为后缀。

前面有讲过,@Component 是依赖双方的联系纽带,现在多了一个 @Module 注解,怎么配合使用呢?方法,很简单。只要在 @component 注解后面的括号中取值就是。

@Component(modules = ShangjiaAModule.class)
public interface WaimaiPingTai 
    ZhaiNan waimai();

然后编写测试代码

mBtnTestModule = (Button) findViewById(R.id.btn_test_module);

final ZhaiNan zainan1 = DaggerWaimaiPingTai.builder()
        .build()
        .waimai();

mBtnTestModule.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        Toast.makeText(MainActivity.this,zainan1.eat(),Toast.LENGTH_LONG).show();
    
);

演示效果如下:

我们再看看 @Provides 用法。

@Module
public class ShangjiaAModule 
    @Provides
     public Baozi provideBaozi() 
        return new Baozi("豆沙包");
    
    @Provides
     public Noodle provideNoodle() 
        return new Noodle();
    

@Provides 注解的方法中直接用 new 创建了依赖,其实还有另外一种方式。我们先对 Noodle 进行重构,让它作为面条的基类,然后编写它一个继承类。

public  class Noodle 
    @Inject
    public Noodle() 
    


public class Tongyi extends Noodle

    @Inject
    public Tongyi() 
    

    @Override
    public String toString() 
        return "统一方便面";
    

ZhanNan 这个类不用改变,然后,用另外一种方式编写 @Provides 注解的方法。

@Module
public class ShangjiaAModule 
    @Provides
     public Baozi provideBaozi() 
        return new Baozi("豆沙包");
    
    @Provides
     public Noodle provideNoodle(Tongyi noodle) 
        return noodle;
    

测试代码也不需要更改,演示效果如下:

那么,两种方式有什么区别呢?

@Provides
public Noodle provideNoodle(Tongyi noodle) 
    return noodle;


@Provides
public Noodle provideNoodle(Tongyi noodle) 
    return noodle;

什么时候用 new 关键字?什么时候直接返回传入进来的参数?
我们不妨再创建一个类 Kangshifu,同样继承自 Noodle 这个基类。

public class Kangshifu extends Noodle

    public Kangshifu() 
    

    @Override
    public String toString() 
        return "康师傅方便面";
    

与 Tongyi 这个类不同的地方是,它并没有用 @Inject 注解构造方法。
我们再尝试更改 @Provides 注解的相应方法。

@Module
public class ShangjiaAModule 
    @Provides
     public Baozi provideBaozi() 
        return new Baozi("豆沙包");
    
    @Provides
     public Noodle provideNoodle(Kangshifu noodle) 
        return noodle;
    
//    @Provides
//     public Noodle provideNoodle(Tongyi noodle) 
//        return noodle;
//    

再进行编译的时候,会发现 IDE 报错了。

Error:(10, 13) 错误: com.frank.dagger2demo.Kangshifu cannot be provided without an @Inject constructor or from an @Provides-annotated method.
com.frank.dagger2demo.Kangshifu is injected at
com.frank.dagger2demo.ShangjiaAModule.provideNoodle(noodle)
com.frank.dagger2demo.Noodle is injected at
com.frank.dagger2demo.ZhaiNan.noodle
com.frank.dagger2demo.ZhaiNan is provided at
com.frank.dagger2demo.WaimaiPingTai.waimai()

Log 提示的错误信息是 Kangshifu 这个类代码中没有被 @Inject 注解过的构造方法,也没有办法从一个被 @Provides 注解过的方法中获取。

所以,什么时候用 new 创建对象,什么时候可以直接返回传入的参数就很明显了。对于被 @Inject 注解过构造方法或者在一个 Module 中的被 @Provides 注解的方法提供了依赖时,就可以直接返回传入的参数,而第三方的库或者 SDK 自带的类就必须手动创建了。

@Module
public class ShangjiaAModule 
    @Provides
     public Baozi provideBaozi() 
        return new Baozi("豆沙包");
    


    @Provides
     public Kangshifu provideKangshifu() 
        return new Kangshifu();
    
//    @Provides
//     public Noodle provideNoodle(Tongyi noodle) 
//        return noodle;
//    

这段代码,是可以正常运行的。

现在,我有一个新的需求更改。我在 ZhaiNan 类中 eat() 方法中要把餐厅名字打印出来。

public class ZhaiNan 

    @Inject
    Baozi baozi;

    @Inject
    Noodle noodle;

    @Inject
    public ZhaiNan() 

    

    @Inject
    String resturant;

    public String eat() 
        StringBuilder sb = new StringBuilder();
        sb.append("我从 ");
        sb.append(resturant.toString());
        sb.append("订的外卖,");
        sb.append("我吃的是 ");
        if ( baozi != null ) 
            sb.append(baozi.toString());
        

        if (noodle != null) 
            sb.append("  ");
            sb.append(noodle.toString());
        
        return sb.toString();
    

注意的是,我要新增了一个 String 类型的字段 resturant 来代表餐厅,并且用 @Inject 注解它。
于是,相应的 Module 也要更改。

@Module
public class ShangjiaAModule 
    @Provides
     public Baozi provideBaozi() 
        return new Baozi("豆沙包");
    
    @Provides
     public Noodle provideNoodle(Kangshifu noodle) 
        return noodle;
    

    @Provides
     public Kangshifu provideKangshifu() 
        return new Kangshifu();
    

    @Provides
     public String provideResturant() 
        return "王小二包子店";
    
//    @Provides
//     public Noodle provideNoodle(Tongyi noodle) 
//        return noodle;
//    

然后,再编译测试。
这次的编译报错了。

Error:(10, 13) 错误: java.lang.String cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
java.lang.String is injected at
com.frank.dagger2demo.ZhaiNan.resturant
com.frank.dagger2demo.ZhaiNan is provided at
com.frank.dagger2demo.Platform.waimai()

报错的原因是之前编写的接口 Platform 没有办法获取 ZhanNan 中 resturant 的依赖。因为之前并没有为 Platform 指定 Module。
那么现在我们就为它指定 ShangjiaAModule 吧。

编译后,进行测试,程序正常运行。

现在,我把代码再重构。在 ShangjiaAModule 中提供餐厅名字的时候,直接返回了“王小二包子店”,这个过于直接,缺少变动,现在针对这个进行变化。

@Module
public class ShangjiaAModule 

    String restaurant;

    public ShangjiaAModule(String restaurant) 
        this.restaurant = restaurant;
    

    ......

    @Provides
     public String provideResturant() 
        return restaurant;
    


restaurant 在 ShangjiaAModule 创建的时候被赋值,但我们之前的代码好像并没有处理 ShangjiaAModule 的创建,那么它如何创建呢?
我们先尝试重新编译代码并运行。

编译没有出错,但运行的时候出错了。

Caused by: java.lang.IllegalStateException: com.frank.dagger2demo.ShangjiaAModule must be set

at com.frank.dagger2demo.DaggerPlatform$Builder.build(DaggerPlatform.java:64)
at com.frank.dagger2demo.MainActivity.onCreate(MainActivity.java:21)

Log 提示的是调用 DaggerPlatform中的Builder.build() 方法时出错了,因为 ShangjiaAModule 并没有出错。我们定位代码。

final ZhaiNan zainan = DaggerPlatform.builder()
                .build()
                .waimai();

我之前没有讲的是,如果一个 Module 没有实现任何构造方法,那么在 Component 中 Dagger2 会自动创建,如果这个 Module 实现了有参的构造方法,那么它需要在 Component 构建的时候手动传递进去。怎么传呢?Component 中生成的 Builder 构造器有与 Module 名字相同的方法,并且参数类型就是 Module 类型。大家细细体会下面代码就明白了。

final ZhaiNan zainan = DaggerPlatform.builder()
                .shangjiaAModule(new ShangjiaAModule("王小二包子店"))
                .build()
                .waimai();


final ZhaiNan zainan1 = DaggerWaimaiPingTai.builder()
                .shangjiaAModule(new ShangjiaAModule("衡阳鱼粉店"))
                .build()
                .waimai();

再编译运行。

另外,还有一种特殊情况就是,像在 Android 中,MainActivity 这样的代码是我们自己编写的,所以我们可以给相应的属性添加 @Inject 注解,但是 MainActivity 对象的创建却是由 Android Framework 框架决定的,那么,Dagger2 有没有针对这种内部拥有 @Inject 标注的属性,但还没有进行依赖绑定的类的对象进行依赖注入呢?答案是肯定的。

我们知道,Component 是一个接口,它里面可以定义很多方法。方法的返回值可以提供一种类型的对象,前提是这个类的对象被 @Inject 注解过构造方法或者在 Module 中被 @Provides 注解过的方法提供。

@Component(modules = ShangjiaAModule.class)
public interface WaimaiPingTai 
    ZhaiNan waimai();

ZhaiNan 能够在一个 Component 中的方法中作为类型返回是因为它符合我上面说的条件,它的构造方法被 @Inject 注解过。

需要注意的是,Component 中方法除了可以返回类型,还可以在方法中传入类型参数。目的是针对这个参数对象进行依赖注入。
比如

@Component(modules = ShangjiaAModule.class)
public interface WaimaiPingTai 
    ZhaiNan waimai();

    void zhuru(ZhaiNan zhaiNan);

我新增了一个方法,zhuru() 中的参数就是 ZhaiNan 类型,代表 DaggerWaimaiPingTai 调用这个方法时能够对一个 ZhaiNan 对象进行依赖注入。

可以编写代码验证。

mBtnTestZhuru = (Button) findViewById(R.id.btn_test_zhuru);
final ZhaiNan zhaiNan = new ZhaiNan();
WaimaiPingTai daggerWaimaiPingTai = DaggerWaimaiPingTai.builder()
        .shangjiaAModule(new ShangjiaAModule("常德津市牛肉粉"))
        .build();
// 通过调用接口中的方法给 zhaiNan 进行依赖注入
daggerWaimaiPingTai.zhuru(zhaiNan);

mBtnTestZhuru.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        Toast.makeText(MainActivity.this,zhaiNan.eat(),Toast.LENGTH_LONG).show();
    
);

运行结果如下:

所以,我们可以给接口方法参数传值的形式来给 Activity 进行依赖注入。

@Module
public class ActivityModule 

    @Provides
    public int provideActivityTest()
        return 1234567890;
    



@Component(modules = ShangjiaAModule.class,ActivityModule.class)
public interface WaimaiPingTai 
    ZhaiNan waimai();

    void zhuru(ZhaiNan zhaiNan);

    void inject(MainActivity mainActivity);
  

我们编写了新的 Module,然后把它放时 WaimaiPingTai 这个 Component 中去,再添加了 inject() 方法,为的是能够给 MainActivity 实例进行依赖注入。

现在我们添加测试代码,首先在 MainActivity 中添加一个 int 类型的成员变量。

@Inject
int testvalue;

然后要调用相关注入方法

mBtnTestActivity = (Button) findViewById(R.id.btn_test_inject_act);
final ZhaiNan zhaiNan = new ZhaiNan();
WaimaiPingTai daggerWaimaiPingTai = DaggerWaimaiPingTai.builder()
        .shangjiaAModule(new ShangjiaAModule("常德津市牛肉粉"))
        .build();

daggerWaimaiPingTai.inject(this);

mBtnTestActivity.setOnClickListener(new View.OnClickListener() 
    @Override
    public void onClick(View v) 
        Toast.makeText(MainActivity.this,"testvalue is "+ testvalue,Toast.LENGTH_LONG).show();
    
);

运行结果:

Component 的创建方式

我们可以看到,创建 Component 都是通过它的 Builder 这个类来进行构建的。其实还有另外一种方式。那就是直接调用 Component 实现类的 create() 方法。

public class Test 

@Component(modules = TestCreate.class)
public interface TestCreateComponent 
    Test ceshi();


TestCreateComponent testCreateComponent = DaggerTestCreateComponent.create();
Test test = testCreateComponent.ceshi();

上面代码中创建 TestCreateComponent 并没有借助于 Builder,而是直接调用了 DaggerTestCreateComponent 的 create() 方法,但是它有一个前提,这个前提就是 Component 中的 module 中被 @Provides 注解的方法都必须是静态方法,也就是它们必须都被 static 修饰。

@Module
public class TestCreate 

    @Provides
    public static int provideTest1() 
        return 1;
    

    @Provides
    public static String provideTest2() 
        return "test component create()";
    

    @Provides
    public static Test provideTest()
        return new Test();
    

因为不需要创建 Module 对象实例,所以 Builder 自然就可以省去了。

@Inject 和 @Provides 的优先级

可能有心思细腻的同学会问,同样是提供依赖,如果一个类被 @Inject 注解了构造方法,又在某个 Module 中的 @Provides 注解的方法中提供了依赖,那么最终 Dagger2 采用的是哪一个?

public class Baozi 

    String name;

    @Inject
    以上是关于轻松学,听说你还没有搞懂 Dagger2的主要内容,如果未能解决你的问题,请参考以下文章

思路不清晰!搞懂这几招轻松月薪过万?学弟学妹“软件测试”加薪有望了

想轻松入门Python编程,这10个经典案例你还不知道嘛?

安琪拉教鲁班学算法之动态规划

轻松搞懂Java中的自旋锁

如果你还没有听说过「聚裂 ReActor」......

你还没搞懂this?