Dagger 2从浅到深(二)

来源:互联网 发布:电脑usb端口怎么设置 编辑:程序博客网 时间:2024/06/01 10:22

Dagger系列:

  1. Dagger 2从浅到深(一)
  2. Dagger 2从浅到深(二)
  3. Dagger 2从浅到深(三)
  4. Dagger 2从浅到深(四)
  5. Dagger 2从浅到深(五)
  6. Dagger 2从浅到深(六)
  7. Dagger 2从浅到深(七)
  8. Dagger 2应用于Android的完美扩展库-dagger.android

在Dagger 2从浅到深(一)中,了解到Dagger依赖注入的基本用法,心里有两个盲点:

  1. @Provides是什么呢?
  2. 注解的构造函数是有参构造函数,该如何传递参数?

带着这两个疑问,开始本文章的话题。

所涉及到的注解

@Provides

@Provides可以认为是对@Inject的补充。对于@Inject不能满足的情况,可以使用@Provides注解方法来满足依赖性,该方法的定义返回类型满足了其依赖关系。不管是接口还是第三方库的类,甚至是相关的配置对象,都可以通过@Provides方法来提供了,以弥补@Inject的盲区。

例如:每当需要StudentBean实例时,都会调用provideStudent方法:

@Provides static StudentBean provideStudent() {  return new StudentBean();}

甚至,@Provides方法本身可能也拥有自身的依赖关系。例如,每当需要StudentBean实例时,都会需要依赖注入AreaBean实例。

@Provides static StudentBean provideStudent(AreaBean area) {  return new StudentBean(area);}

按照惯例,对于@Provides方法的命名以provide为前缀,例如, provideXx.

值得注意的是,@Provides方法本身是不能独立存在的,它必须依附于一个Module。所谓的Module是具有@Module注解的类。关于@Moduel是什么?后续即将讲解。

@Module

@Module源码:

public @interface Module {  /**   * 本身依赖的其他的Module   */  Class<?>[] includes() default {};  /**   * 注入该模块的子组件   * 一个子组件中,可以注入多个Module   */  @Beta  Class<?>[] subcomponents() default {};}

官方对于@Module的解释是这样的: Annotates a class that contributes to the object graph.,翻译过来就是,其注解的类为Dagger提供依赖关系。

之前已经提到了,@Inject可以提供依赖关系,但是其不是万能的。如果我们所需要的提供的构造函数没有使用@Inject注解,比如,第三方库里的类,我们并没有权限修改源码。这时,Module类可以在不修改源码构造函数的情况下,提供依赖关系。即使是可以用@Inject注解的,依然可以通过Module提供依赖关系。

这里,我们需要明确的一个概念是@Module注解的类,是向Dagger提供依赖关系。具体该如何使用,后续更精彩!

@Component

@Compoent部分源码解析:

public @interface Component {  /**   * 注入的Module列表,用于组件的实现   * 这些Module可能依赖于其他的Module,这里只需要列出注入的Module即可   */  Class<?>[] modules() default {};  /**   * 可用于依赖关系的组件列表   */  Class<?>[] dependencies() default {};}

不管@Inejct还是@Provide方法,形成了由它们的依赖关系链接做组成的IOC容器。这时,应用程序应可以通过一个桥接调用IOC中实例,以完成依赖的注入的,那就不得不提@Component。@Component一般用来注解接口,被注解的接口在编译时会产生相应的实例,作为提供依赖和所需依赖之间的桥梁,把相关依赖注入到其中。

编译时,被@Component注解的接口在编译时会产生相应的实例的名称一般以Dagger为前缀。比如,接口名称为StudentComponent,那么编译时生成的实例名称为DaggerStudentComponent.

@Component(modules = DripCoffeeModule.class)interface CoffeeShop {  CoffeeMaker maker();}

初始化

在编译时,已经实现了@Component注解的接口,我们可以通过调用该实现的builder()方法,获取相应的Builder实例,并使用返回的Builder设置依赖关系,然后调用build()方法创建一个新的实例。

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()    .dripCoffeeModule(new DripCoffeeModule())    .build();


注意:

  1. 如果您的@Component不是top_level类,则生成的组件的名称将包含其包围类型的名称,并加上下划线。 例如,这段代码:

    class Foo {  static class Bar {    @Component    interface BazComponent {}  }}

    将生成一个名为DaggerFoo_Bar_BazComponent的组件。

  2. 对于具有默认构造函数的Module,可以被忽略。如果没有设置,Builder将自动创建一个实例。
  3. 对于所有的@Provides方法都是静态的Module,没有必要创建Module实例
  4. 如果所有的依赖关系在没有设置的情况下创建@Component实例,也就是说Builder可以自动创建实例,此时我们调用create方法来创建过一个@Component实例,而不用Builder

    @Component(modules = DripCoffeeModule.class)interface CoffeeShop {  CoffeeMaker maker();}public class CoffeeApp {  public static void main(String[] args) {    CoffeeShop coffeeShop = DaggerCoffeeShop.create();    coffeeShop.maker().brew();  }}

依赖注入的结构

Dagger2要实现一个完整的依赖注入,必不可少的元素有三种,Module,Component,Container。

  1. Container就是可以被注入的容器,具体对应上文例子中的Container,Container拥有需要被初始化的元素。需要被初始化的元素必须标上@Inject,只有被标上@Inject的元素才会被自动初始化。@Inject在Dagger2中一般标记构造方法与成员变量。
  2. Module 可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从Module生产的。
  3. 有了可以被注入的容器Container,也有了提供依赖对象的Module。我们必须将依赖对象注入到容器中,这个过程由Component来执行。Component将Module中产生的依赖对象自动注入到Container中。

依赖关系

  • 在@Module中,由@Provides注解的方法,可以由@Component.modules直接饮用,或通过@Module.includes引用
  • 任何类型的构造被@Inject注解的构造函数,不管是否与该组建的的生命周期相关联(是否被@Scope注释)
  • 组件依赖关系所配置的方法
  • 组件本身
  • 任何包含的子组件的不合格建造者
  • Provider或Lazy包装器用于上述任何绑定
  • 任何上述绑定的延迟提供者(例如,Provider

简单示例

1.实现@Module和@Provides注解

Module其实就是依赖关系的加工厂,我们只需在添加制造依赖的方法即可。现创建两个Module:OrangeModule和FruitModule,而FruitModule又依赖于OrangeModule。

@Modulepublic class OrangeModule {    OrangeBean orangeBean;    // 有参构造函数    // 标明该Module必须初始化    // 如果未初始化,编译时不会报错,    // 但,在运行时报错“IllegalStateException”,提示初始化该Module    public OrangeModule(OrangeBean orangeBean) {        this.orangeBean = orangeBean;    }    // 表明该方法是用来提供依赖对象的特殊方法    // 提供依赖对象 OrangeBean    @Provides    public OrangeBean provideOrange() {        return this.orangeBean;    }}// 注明本类属于Module@Module(includes = OrangeModule.class)public class FruitModule {    // 表明该方法是用来提供依赖对象的特殊方法    // 提供依赖对象 AppleBean    @Provides    public AppleBean provideApple() {        return new AppleBean("红富士", 4.55);    }    // 表明该方法是用来提供依赖对象的特殊方法    // 提供依赖对象 Fruits    // @Provides    @Provides    public Fruits provideFruits(AppleBean appleBean, OrangeBean orangeBean) {        return new Fruits(appleBean, orangeBean);    }}


注意:

  • @Module注解类,表明该类是一个Module
  • @Provides注解方法,表明方法是用来提供依赖对象的特殊方法
  • 一个完整的Module必须拥有@Module与@Provides注解

    1. 实现Component

Component就是一个将Module生成的实例注入Container中的注入器。下面创建FruitComponent,其注入的Module为FruitModule。

// modules 指明Component在哪些Module中查找依赖@Component(modules = {FruitModule.class})public interface FruitComponent {    void inject(FruitActivity activity);    AppleBean makeApple();}


注意:

  • @Component中使用modules,表明该Component在哪些注入的Module中查找依赖
  • @Component中使用dependencie,表明该Component在哪些注入的Component中查找依赖
  • 添加注入方法,一般使用inject
  • 可以声明方法,提供注入的实例


3. 创建Component实例

创建Component实例,我们就可以从Container中提取注入的实例。

public class FruitActivity extends AppCompatActivity {    ***         @Inject    OrangeBean orangeBean;    @Inject    AppleBean appleBean;    @Inject    Fruits fruits;    @Override    protected void onCreate(Bundle savedInstanceState) {        FruitComponent fruitComponent = DaggerFruitComponent.builder()                                                .orangeModule(new OrangeModule(new OrangeBean("贡菊", 6.88, "江西南昌")))                                                .build();        fruitComponent.inject(this);        super.onCreate(savedInstanceState);        setContentView(getLayoutId());        ****        //  orangeBean:OrangeBean{name='贡菊', price=6.88, area='江西南昌'}        Log.d("test", "orangeBean:" + orangeBean.toString());        //  appleBean:AppleBean{name='红富士', price=4.55}        Log.d("test", "appleBean:" + appleBean.toString());        // fruits:Fruits{appleBean=AppleBean{name='红富士', price=4.55}, orangeBean=OrangeBean{name='贡菊', price=6.88, area='江西南昌'}}        Log.d("test", "fruits:" + fruits.toString());        AppleBean apple = fruitComponent.makeApple();        // apple:AppleBean{name='红富士', price=4.55}        Log.d("test", "apple:" + apple.toString());        tvFruit.setText(fruits.toString());    }}


注意:

  1. 一个Component可以包含多个Module或者Component,这样Component获取依赖时候会自动从多个Module中查找获取,但是,Module间不能有重复方法。

    添加多个Module有两种方法

    1. 在Component中的modules属性中注入多个Module

      @Component(modules={ModuleA.class,ModuleB.class,...})public interface FruitComponent{    ...}
    2. 在Component中的dependencies属性中注入多个依赖的Component

      @Component(dependencies={ComponentA.class,ComponentB.class,...}) public interface FruitComponent{    ...}
    3. 在Module中注入多个Module

      @Module(includes={ModuleA.class,ModuleB.class,...})public class FruitModule{    ...}
  2. 注入的Module,不管是属于Component本身还是注入的Module的依赖,如果其构造函数为有参构造函数,必须进行初始化。

    DaggerFruitComponent.builder()    .orangeModule(new OrangeModule(new OrangeBean("贡菊", 6.88, "江西南昌")))    .build();
  3. 返回类型相同的@Provides 方法必须是唯一的。如果不唯一,Dagger 2不知道如何去区分,也就是所谓的“依赖迷失”。鉴于Java的多态性-重载,这种情况必然会出现。如何解决“依赖迷失”,后续介绍。
  4. 在Component中,可以声明方法,比如,makeApple(),直接提供注入对象实例。如果看过Dagger 编译过的DaggerFruitComponent源码的,可以知道,在编译时,针对每一个@Provides方法创建Provider&ltT>实例。在调用makeApple()方法时,实际上是调用的相应Provider的get()方法,获取相应的实例。有兴趣的,可以跟踪源码看看。

    public AppleBean makeApple() {    return provideAppleProvider.get();}

依赖规则

不管是@Injdec还是@Module都提供了依赖对象,它们注入的优先级是怎样的呢?Dagger 2有自己的依赖规则:

  1. 步骤1:查找Module中是否存在创建该类的方法。
  2. 步骤2:若存在创建类方法,查看该方法是否存在参数

    1. 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
    2. 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
  3. 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

    1. 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
    2. 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

总结

到这,Dagger 2的基本用法算是结束了,Dagger 2的功能何其强大,远远不如此,仍需继续学习。后续将解决前面提到的“依赖迷失”。

本文中很多地方都是重复提到,每一次重复都是一次血泪史,教训太深刻。多一遍查看,给自己敲响警钟,不要再犯这二笔的错误。

参考文档

  1. 浅析Dagger2的使用
  2. Android:dagger2让你爱不释手-基础依赖注入框架篇
  3. Android:dagger2让你爱不释手-重点概念讲解、融合篇
  4. Android:dagger2让你爱不释手-终结篇
  5. “一盘沙拉”带你入门Dagger2(一)之HelloWorld
  6. Dagger2图文完全教
  7. 为什么网上这么多dagger2教程,我还写了这篇文章。
  8. Android常用开源工具(1)-Dagger2入门
2 0