一、Dagger2简介
Dagger2起源于Dagger,是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。
可见,Dagger2框架的核心编程思想:依赖注入(DI)
那么,什么是依赖注入呢?
这里,谈起依赖注入,做过J2EE开发的同学一定会想起Spring框架中的:控制反转(IoC)
此处借鉴了几位技术大神的文章,通俗易懂,特此分享:
- Iteye对 IoC 和 DI 的理解,原文地址:https://jinnianshilongnian.iteye.com/blog/1413846
- 门心叼龙对Dagger2的理解,原文地址:https://blog.youkuaiyun.com/geduo_83/article/details/90380191
- Val0050对Dagger2的理解,原文地址:https://www.open-open.com/lib/view/open1482201981550.html#articleHeader6
二、Dagger2核心
Dagger2框架的核心编程思想:控制反转(IoC)和依赖注入(DI)
2.1 控制反转(IoC)是什么?
IoC—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,IoC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
如何理解好IoC呢?理解好IoC的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
-
谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
-
为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2-2所示:
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一——好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
2.2 依赖注入(DI)是什么?
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
- 谁依赖于谁:当然是应用程序依赖于IoC容器;
- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
- 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
2.3 IoC 和 DI 有什么关系呢?
其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
2.4 三种常见的依赖注入方式
2.4.1、构造注入: 通过给构造函数传参给依赖的成员变量赋值,从而实现注入。
public class Car{
private Engine engine;
public Car(Engine engine){
this.engine = engine;
}
}
2.4.2、接口注入: 实现接口方法,同样以传参的方式实现注入。
public interface Injection<T>{
void inject(T t);
}
public class Car implements Injection<Engine>{
private Engine engine;
public Car(){}
public void inject(Engine engine){
this.engine = engine;
}
}
2.4.3、注解注入: 使用Java注解在编译阶段生成代码实现注入或者是在运行阶段通过反射实现注入。
public class Car{
@Inject
Engine engine;
public Car(){}
}
前两种注入方式需要我们编写大量的模板代码,而机智的Dagger2则是通过Java注解在编译期来实现依赖注入的。
三、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使用
前面长篇大论的基本都在介绍概念,下面我们看看Dagger2的基本应用。我们还是拿前面的Car和Engine来举例。
1、案例A
Car类是需求依赖方,依赖了Engine类;因此我们需要在类变量Engine上添加@Inject来告诉Dagger2来为自己提供依赖。
public class Car {
@Inject
Engine engine;
public Car() {
DaggerCarComponent.builder().build().inject(this);
}
public Engine getEngine() {
return this.engine;
}
}
Engine类是依赖提供方,因此我们需要在它的构造函数上添加@Inject
public class Engine {
@Inject
Engine(){}
public void run(){
System.out.println("引擎转起来了~~~");
}
}
接下来我们需要创建一个用@Component标注的接口CarComponent,这个CarComponent其实就是一个注入器,这里用来将Engine注入到Car中。
@Component
public interface CarComponent {
void inject(Car car);
}
完成这些之后我们需要Build下项目,让Dagger2帮我们生成相关的Java类。接着我们就可以在Car的构造函数中调用Dagger2生成的DaggerCarComponent来实现注入(这其实在前面Car类的代码中已经有了体现)
public Car() {
DaggerCarComponent.builder().build().inject(this);
}
2、案例B
如果创建Engine的构造函数是带参数的呢?比如说制造一台引擎是需要齿轮(Gear)的。或者Eggine类是我们无法修改的呢?这时候就需要@Module和@Provide上场了。
同样我们需要在Car类的成员变量Engine上加上@Inject表示自己需要Dagger2为自己提供依赖;Engine类的构造函数上的@Inject也需要去掉,应为现在不需要通过构造函数上的@Inject来提供依赖了。
public class Car {
@Inject
Engine engine;
public Car() {
DaggerCarComponent.builder().markCarModule(new MarkCarModule())
.build().inject(this);
}
public Engine getEngine() {
return this.engine;
}
}
接着我们需要一个Module类来生成依赖对象。前面介绍的@Module就是用来标准这个类的,而@Provide则是用来标注具体提供依赖对象的方法(这里有个不成文的规定,被@Provide标注的方法命名我们一般以provide开头,这并不是强制的但有益于提升代码的可读性)。
@Module
public class MarkCarModule {
public MarkCarModule(){ }
@Provides Engine provideEngine(){
return new Engine("gear");
}
}
接下来我们还需要对CarComponent进行一点点修改,之前的@Component注解是不带参数的,现在我们需要加上modules = {MarkCarModule.class},用来告诉Dagger2提供依赖的是MarkCarModule这个类。
@Component(modules = {MarkCarModule.class})
public interface CarComponent {
void inject(Car car);
}
Car类的构造函数我们也需要修改,相比之前多了个markCarModule(new MarkCarModule())方法,这就相当于告诉了注入器DaggerCarComponent把MarkCarModule提供的依赖注入到了Car类中。
public Car() {
DaggerCarComponent.builder()
.markCarModule(new MarkCarModule())
.build().inject(this);
}
这样一个最最基本的依赖注入就完成了,接下来我们测试下我们的代码。
public static void main(String[] args){
Car car = new Car();
car.getEngine().run();
}
输出
引擎转起来了~~~
3、案例C
那么如果一台汽车有两个引擎(也就是说Car类中有两个Engine变量)怎么办呢?没关系,我们还有@Qulifier!首先我们需要使用Qulifier定义两个注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierA { }
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierB { }
同时我们需要对依赖提供方做出修改
@Module
public class MarkCarModule {
public MarkCarModule(){ }
@QualifierA
@Provides
Engine provideEngineA(){
return new Engine("gearA");
}
@QualifierB
@Provides
Engine provideEngineB(){
return new Engine("gearB");
}
}
接下来依赖需求方Car类同样需要修改
public class Car {
@QualifierA @Inject Engine engineA;
@QualifierB @Inject Engine engineB;
public Car() {
DaggerCarComponent.builder().markCarModule(new MarkCarModule())
.build().inject(this);
}
public Engine getEngineA() {
return this.engineA;
}
public Engine getEngineB() {
return this.engineB;
}
}
最后我们再对Engine类做些调整方便测试
public class Engine {
private String gear;
public Engine(String gear){
this.gear = gear;
}
public void printGearName(){
System.out.println("GearName:" + gear);
}
}
测试代码
public static void main(String[] args) {
Car car = new Car();
car.getEngineA().printGearName();
car.getEngineB().printGearName();
}
执行结果:
GearName:gearA
GearName:gearB
4、案例D
接下来我们看看@Scope是如何限定作用域,实现局部单例的。
首先我们需要通过@Scope定义一个CarScope注解:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CarScope {
}
接着我们需要用这个@CarScope去标记依赖提供方MarkCarModule。
@Module
public class MarkCarModule {
public MarkCarModule() {
}
@Provides
@CarScope
Engine provideEngine() {
return new Engine("gear");
}
}
同时还需要使用@Scope去标注注入器Compoent
@CarScope
@Component(modules = {MarkCarModule.class})
public interface CarComponent {
void inject(Car car);
}
为了便于测试我们对Car和Engine类做了一些改造:
public class Car {
@Inject Engine engineA;
@Inject Engine engineB;
public Car() {
DaggerCarComponent.builder()
.markCarModule(new MarkCarModule())
.build().inject(this);
}
}
public class Engine {
private String gear;
public Engine(String gear){
System.out.println("Create Engine");
this.gear = gear;
}
}
如果我们不适用@Scope,上面的代码会实例化两次Engine类,因此会有两次"Create Engine"输出。现在我们在有@Scope的情况测试下劳动成果:
public static void main(String[] args) {
Car car = new Car();
System.out.println(car.engineA.hashCode());
System.out.println(car.engineB.hashCode());
}
输出
Create Engine
bingo!我们确实通过@Scope实现了局部的单例。
五、Dagger2原理
前面介绍了Dagger2的基本使用,接下来我们再分析分析实现原理。这里不会分析Dagger2根据注解生成各种代码的原理,关于Java注解以后有机会再写一篇文章来介绍。后面主要分析的是Dagger2生成的各种类如何帮我们实现依赖注入,为了便于理解我这里选了前面相对简单的案例B来做分析。
Dagger2编译期生成的代码位于build/generated/source/apt/debug/your package name/下面:
首先我们看看Dagger2依据依赖提供方MarkCarModule生成的对应工厂类MarkCarModule_ProvideEngineFactory。为了方便大家理解对比,后面我一律会把自己写的类和Dagger2生成的类一并放出来。
/**
*我们自己的类
*/
@Module
public class MarkCarModule {
public MarkCarModule(){ }
@Provides Engine provideEngine(){
return new Engine("gear");
}
}
/**
*Dagger2生成的工厂类
*/
public final class MarkCarModule_ProvideEngineFactory implements Factory<Engine> {
private final MarkCarModule module;
public MarkCarModule_ProvideEngineFactory(MarkCarModule module) {
assert module != null;
this.module = module;
}
@Override
public Engine get() {
return Preconditions.checkNotNull(
module.provideEngine(), "Cannot return null from a non-@Nullable @Provides method");
}
public static Factory<Engine> create(MarkCarModule module) {
return new MarkCarModule_ProvideEngineFactory(module);
}
/** Proxies {@link MarkCarModule#provideEngine()}. */
public static Engine proxyProvideEngine(MarkCarModule instance) {
return instance.provideEngine();
}
}
我们可以看到MarkCarModule_ProvideEngineFactory中的get()调用了MarkCarModule的provideEngine()方法来获取我们需要的依赖Engine,MarkCarModule_ProvideEngineFactory的实例化有crate()创建,并且MarkCarModule的实例也是通过create()方法传进来的。那么这个create()一定会在哪里调用的,我们接着往下看。
前面提到@Component是依赖提供方(MarkCarModule)和依赖需求方(Car)之前的桥梁,那我看看Dagger2是如何通过CarComponent将两者联系起来的。
/**
*我们自己的类
*/
@Component(modules = {MarkCarModule.class})
public interface CarComponent {
void inject(Car car);
}
/**
*Dagger2生成的CarComponent实现类
*/
public final class DaggerCarComponent implements CarComponent {
private Provider<Engine> provideEngineProvider;
private MembersInjector<Car> carMembersInjector;
private DaggerCarComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static CarComponent create() {
return builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.provideEngineProvider = MarkCarModule_ProvideEngineFactory.create(builder.markCarModule);
this.carMembersInjector = Car_MembersInjector.create(provideEngineProvider);
}
@Override
public void inject(Car car) {
carMembersInjector.injectMembers(car);
}
public static final class Builder {
private MarkCarModule markCarModule;
private Builder() {}
public CarComponent build() {
if (markCarModule == null) {
this.markCarModule = new MarkCarModule();
}
return new DaggerCarComponent(this);
}
public Builder markCarModule(MarkCarModule markCarModule) {
this.markCarModule = Preconditions.checkNotNull(markCarModule);
return this;
}
}
}
通过上面的代码我们看到Dagger2依据CarComponent接口生成了实现类DaggerCarComponent(没错这正是我们在Car的构造函数中使用DaggerCarComponent)。DaggerCarComponent在build的时候实例化了DaggerCarComponent对象,并首先调用MarkCarModule_ProvideEngineFactory.create(builder.markCarModule)始化了provideEngineProvider变量,接着调用Car_MembersInjector.create(provideEngineProvider)初始化了carMembersInjector变量。当我们手动在Car类的构造函数中调用inject(Car car)方法时会执行carMembersInjector.injectMembers(car)。所以接下来我们要看看Car_MembersInjector的实现。
public final class Car_MembersInjector implements MembersInjector<Car> {
private final Provider<Engine> engineProvider;
public Car_MembersInjector(Provider<Engine> engineProvider) {
assert engineProvider != null;
this.engineProvider = engineProvider;
}
public static MembersInjector<Car> create(Provider<Engine> engineProvider) {
return new Car_MembersInjector(engineProvider);
}
@Override
public void injectMembers(Car instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
instance.engine = engineProvider.get();
}
public static void injectEngine(Car instance, Provider<Engine> engineProvider) {
instance.engine = engineProvider.get();
}
}
Car_MembersInjector中的create()用于实例化自己,这个方法前面我们看到是在DaggerCarComponent中调用的。injectMembers(Car instance)将engineProvider.get()的返回值赋给了依赖需求方Car的engine变量,而engineProvider.get()正是本节一开始我们提到的MarkCarModule_ProvideEngineFactory中的get()方法。至此整个依赖注入的流程就完成了。更复杂的应用场景会生成更加复杂的代码,但原理都和前面分析的大同小异。