Dagger 2 完全解析(一),基本使用与原理

Dagger 2 完全解析(一),基本使用与原理


Dagger 2 完全解析(一),基本使用与原理
Dagger 2 完全解析(二), 进阶使用
Dagger 2 完全解析(三), Component 与 SubComponent

本系列文章是基于 Google Dagger 2.23.2 版本, Kotlin 1.3.21版本


依赖注入

什么是依赖

依赖(Dependency) 是类与类之间的联接。依赖关系表示一个类依赖于另一个类的定义, 一般而言,依赖关系在语言中体现为局部变量、方法的形参,或者对静态方法的调用。

我们在Android开发中 经常会在build.gradle中引入第三方包的依赖包,然后就完成了对第三方的依赖。那么到底什么是依赖呢?下面我举个例子:

假如有两个 : AB, A中持有B的实例,则可认为 A依赖B

// Class A
class A(
    private val b: B = B()// A中定义了b,并创建了B实例
)

// Class B
class B

上面这种写法是最常见的写法,但是在下面几个场景中存在一些问题:

  1. 如果要修改 B 的构造函数,例如需要使用b= B(name)的方式构造时,还要修改 A的代码;
  2. 不利于单元测试,如单元测试中使用 mock 的 B测试A

什么是依赖注入(Dependency Injection)

依赖注入(简称 DI)是用于实现控制反转IOC)最常见的方式之一,IOC是面向对象编程中的一种设计思想,用以降低代码之间耦合度。控制反转的基本思想是:借助“第三方”实现具有依赖关系的对象之间的解耦。

就拿上面的例子来说,我们在A中创建并持有B的实例,A依赖B ,并且创建的主动权在A, 实现了 Ioc 后,对象 A 依赖于IOC 容器,对象 A 被动地接受容器提供的对象 B 实例,由主动变为被动,因此称为控制反转。

注意,控制反转不等同于依赖注入,控制反转还有一种实现方式叫“依赖查找”(Denpendency Lookup)

依赖注入就是将对象实例传入到一个对象中去(Denpendency injection means giving an object its instance variables)。依赖注入是一种设计模式,降低了依赖和被依赖对象之间的耦合,方便扩展和单元测试。

依赖注入的实现方式

其实在平常编码的过程中,已经不知觉地使用了依赖注入

  • 基于构造函数,在构造对象时注入所依赖的对象。
class A(
    private val b: B // 由创建A 是 传入B的实例
)
  • 基于 set 方法,使用 setter 方法来让外部容器调用传入所依赖的对象。
class A {
    private lateinit var b: B

    // 通过外部调用setB()实现实例注入
    fun setB(b: B) {
        this.b = b
    }
}
  • 基于接口,使用接口来提供 setter 方法。
interface BInjector {
    fun injectB(b: B)
}

class A : BInjector {
    private var b: B? = null

    override fun injectB(b: B) {
        this.b = b
    }
}
  • 基于注解,Dagger 2 依赖注入框架就是使用@Inject完成注入。
class A {
    @Inject
    lateinit var b: B
}

Dagger 2

Dagger 2JavaAndroid下的一个完全静态、编译时生成代码的依赖注入框架,由 Google 维护,早期的版本 Dagger 是由 Square 创建的。

Dagger 2 是基于 Java Specification Request(JSR) 330标准。利用 JSR 注解在编译时生成代码,来注入实例完成依赖注入。

下面是 Dagger 2 的一些资源地址:

Github:https://github.com/google/dagger

官方文档:https://google.github.io/dagger//

API:http://google.github.io/dagger/api/latest/

Dagger 2 的基本使用

上面介绍了依赖注入和 Dagger 2,下面由简单的示例开始一步一步地解析 Dagger 2 的基本使用与原理。

引入 Dagger 2

build.gradle中添加依赖和plugin

apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.dagger:dagger:2.23.2'
    kapt 'com.google.dagger:dagger-compiler:2.32.2'
}

如果 Android gradle plugin 的版本低于2.2,还需要引入 android-apt 插件。

使用 @Inject 标注需要注入的依赖

@Inject

package javax.inject;

@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}

继续使用上面 A 的例子:

class A {
    @Inject
    lateinit var b: B
}

使用javax.inject.Inject注解来标注需要 Dagger 2 注入的依赖,make module或者build后可以在build/generated/source/kapt目录下看到 Dagger 2 编译时生成的成员属性注入类。

public final class A_MembersInjector implements MembersInjector<A> {
  private final Provider<B> bProvider;

  public A_MembersInjector(Provider<B> bProvider) {
    this.bProvider = bProvider;
  }

  public static MembersInjector<A> create(Provider<B> bProvider) {
    return new A_MembersInjector(bProvider);}

  @Override
  public void injectMembers(A instance) {
    injectB(instance, bProvider.get());
  }

  public static void injectB(A instance, B b) {
    instance.b = b;
  }
}

从上面的injectMembers方法中可以看到注入依赖的代码是instance.b= bProvider.get();,所以@Inject标注的成员属性不能是private的,不然无法注入。

创建所依赖对象的实例

@Inject标注构造函数时,Dagger 2 会完成实例的创建。

class B @Inject constructor()

build 后可以在build/generated/source/kapt目录下看到 Dagger 2 编译时生成的工厂类。

public final class B_Factory implements Factory<B> {
  private static final B_Factory INSTANCE = new B_Factory();

  @Override
  public B get() {
    return new B();
  }

  public static B_Factory create() {
    return INSTANCE;
  }

  public static B newInstance() {
    return new B();
  }
}

依赖注入是依赖的对象实例–>需要注入的实例属性,上面完成两步,通过 Dagger 2 生成的代码代码可以知道,生成了 A的成员属性注入类和 B的工厂类,接下来需要的就是新建工厂实例并调用成员属性注入类完成 B的实例注入。完成这个过程的桥梁就是dagger.Component

Component 桥梁

@Component可以标注接口或抽象类

@Retention(RUNTIME) 
@Target(TYPE)
@Documented
public @interface Component {
  @Target(TYPE)
  @Documented
  @interface Builder {}

  @Target(TYPE)
  @Documented
  @interface Factory {}
}

Component可以完成依赖注入过程,其中最重要的是定义注入接口,调用注入接口就可以完成 A 所需依赖的注入。

@Component
interface AComponent {
    fun injectA(a: A)
}

build 后会生成带有Dagger前缀的实现该接口的类:DaggerManComponent

public final class DaggerAComponent implements AComponent {
  private DaggerAComponent() {

  }

  public static Builder builder() {
    return new Builder();
  }

  public static AComponent create() {
    return new Builder().build();
  }

  @Override
  public void injectA(A a) {
    injectA2(a);
  }

  private A injectA2(A instance) {
    A_MembersInjector.injectB(instance, new B());
    return instance;
  }

  public static final class Builder {
    private Builder() {
    }

    public AComponent build() {
      return new DaggerAComponent();
    }
  }
}

从上面生成的代码可以看出来 DaggerAComponent就是连接依赖的对象A实例需要注入的B实例属性之间的桥梁。DaggerAComponent会查找目标类对应的成员属性注入类(即A_MembersInjector),然后调用A_MembersInjector.injectB(instance, new B())就能完成依赖注入。注意,Component 中注入接口的参数必须为需要注入依赖的类型,不能是 要注入类型的父类或子类,注入接口返回值为 void,接口名可以任意。

接下来只需要在 A中调用injectA方法就能完成注入。

class Man {
	init{
        DaggerManComponent.create().injectA(this)
	} 
}

Module

使用@Inject标注构造函数来提供依赖的对象实例的方法,不是万能的,在以下几种场景中无法使用:

  • 接口没有构造函数
  • 第三方库的类不能被标注
  • 构造函数中的参数必须配置

为了解决上面的问题,Dagger又提供了一种新的注解方式:Module

Module中,Module 即用@Module标注的类。所以 Module 是提供依赖的对象实例的另一种方式。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Module {
 
  Class<?>[] includes() default {};

  @Beta
  Class<?>[] subcomponents() default {};
}

Module可以用@Provides标注的方法来提供依赖实例,方法的返回值就是依赖的对象实例。

@Module
class AModule {
    @Provides
    fun provideB(): B = B()
}

约定俗成的是@Provides方法一般以provide为前缀,Moudle 类以Module为后缀,一个 Module 类中可以有多个@Provides方法。

接下来,关联Module与Component:

@Component(modules = [AModule::class])
interface AComponent {
    fun injectA(a: A)
}

build之后,Module 和 Component 生成的类为:

public final class DaggerAComponent implements AComponent {
  private final AModule aModule;

  private DaggerAComponent(AModule aModuleParam) {
    this.aModule = aModuleParam;
  }
  ....

private A injectA2(A instance) {
    A_MembersInjector.injectB(instance, AModule_ProvideBFactory.provideB(aModule));
    return instance;
  }

  public static final class Builder {
    private AModule aModule;

    private Builder() {
    }

    public Builder aModule(AModule aModule) {
      this.aModule = Preconditions.checkNotNull(aModule);
      return this;
    }

    public AComponent build() {
      if (aModule == null) {
        this.aModule = new AModule();
      }
      return new DaggerAComponent(aModule);
    }
  }
}

生成的 DaggerAComponent 和之前相比主要是injectA2()方法不一样,具体体现在:

没有Module时:

A_MembersInjector.injectB(instance, new B());

Module时:

 A_MembersInjector.injectB(instance, AModule_ProvideBFactory.provideB(aModule));

AModule_ProvideBFactory实现 Factory 接口。

public final class AModule_ProvideBFactory implements Factory<B> {
  private final AModule module;

  public AModule_ProvideBFactory(AModule module) {
    this.module = module;
  }

  @Override
  public B get() {
    return provideB(module);
  }

  public static AModule_ProvideBFactory create(AModule module) {
    return new AModule_ProvideBFactory(module);
  }

  public static B provideB(AModule instance) {
    return Preconditions.checkNotNull(instance.provideB(), "Cannot return null from a non-@Nullable @Provides method");
  }
}

总结

现在再来看 Dagger 2 最核心的三个部分:

  1. 需要注入依赖的目标类,需要注入的实例属性由@Inject标注。
  2. 提供依赖对象实例的工厂,用@Inject标注构造函数或定义Module这两种方式都能提供依赖实例,Dagger 2 的注解处理器会在编译时生成相应的工厂类。Module的优先级比@Inject标注构造函数的高,意味着 Dagger 2 会先从 Module 寻找依赖实例。
  3. 把依赖实例工厂创建的实例注入到目标类中的 Component。

下面再讲述上面提到的在 Dagger 2 种几个注解的用法:

  • @Inject 一般情况下是标注成员属性和构造函数,标注的成员属性不能是private,Dagger 2 还支持方法注入,@Inject还可以标注方法。
  • @Provides 只能标注方法,必须在 Module 中。
  • @Module 用来标注 Module 类
  • @Component 只能标注接口或抽象类,声明的注入接口的参数类型必须和目标类一致。
posted @ 2019-06-21 22:02  jxiaow  阅读(268)  评论(0编辑  收藏  举报