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
中引入第三方包的依赖包,然后就完成了对第三方的依赖。那么到底什么是依赖呢?下面我举个例子:
假如有两个类
: A
和B
, A
中持有B
的实例,则可认为 A
依赖B
// Class A
class A(
private val b: B = B()// A中定义了b,并创建了B实例
)
// Class B
class B
上面这种写法是最常见的写法,但是在下面几个场景中存在一些问题:
- 如果要修改
B
的构造函数,例如需要使用b= B(name)
的方式构造时,还要修改A
的代码; - 不利于单元测试,如单元测试中使用 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 2
是 Java
和Android
下的一个完全静态、编译时生成代码的依赖注入框架,由 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 最核心的三个部分:
- 需要注入依赖的目标类,需要注入的实例属性由
@Inject
标注。 - 提供依赖对象实例的工厂,用
@Inject
标注构造函数或定义Module
这两种方式都能提供依赖实例,Dagger 2 的注解处理器会在编译时生成相应的工厂类。Module的优先级比@Inject标注构造函数的高,意味着 Dagger 2 会先从 Module 寻找依赖实例。 - 把依赖实例工厂创建的实例注入到目标类中的 Component。
下面再讲述上面提到的在 Dagger 2 种几个注解的用法:
@Inject
一般情况下是标注成员属性和构造函数,标注的成员属性不能是private
,Dagger 2 还支持方法注入,@Inject
还可以标注方法。@Provides
只能标注方法,必须在 Module 中。@Module
用来标注 Module 类@Component
只能标注接口或抽象类,声明的注入接口的参数类型必须和目标类一致。