依赖注入框架 ----Dagger2 使用详解及源码分析
在开始说Dagger之前先说下什么叫依赖注入。
依赖:
在创建对象A的过程中,需要用到对象B的实例,这种情况较调用者A对被调用者B有一个依赖。
例如下面的例子: 组装一台电脑时,要用到Cpu,那么电脑这个对象,依赖Cpu对象。
public class Computer {
CPU cpu;
public Computer(CPU cpu) {
this.cpu = cpu;
}
}
这个例子中Computer的这种初始化也叫HardInit (HI)方式,弊端在于两个类之间不够独立,如果我们更改了Cpu的构造函数,那么所有使用到Cpu的初始化方法的代码都要进行更改。
依赖注入:
指程序运行过程中,调用者(Computer)需要被调用者的辅助(CPU),但是创建被调用者(CPU)的工作不再由调用者(Computer)来完成,而是由相关的容器控制程序被调用者 (CPU)的对象在外部去创建出来并注入到调用者的引用中(Computer),因此也称为控制反转(IOC: Inversion of Control)。
如下面:
public class Computer {
//注入CPU
@Inject
Intel_CPU mCPU;
public Computer() {
}
}
那为什么使用依赖注入?
看完上面的例子其实可以领会到它的作用了。依赖注入是实现控制反转的方式之一(另一种是控制查找),目的就是为了让调用者和被调用者之间的解耦;
可以注入依赖的模拟实现,使得测试变得更加简单。
好了,再来介绍一遍: Dagger2起源于Dagger,Dagger是由Squre公司开发出的依赖注入开源库,但是还是存在一些性能问题,因此Google在Dagger的基础上进行了改造演变出了现在的Dagger2.
Dagger就是一款基于Java注解实现完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦,提高代码的健壮性和可维护性。
ok,道理我都懂了,那怎么用呐?
先来说下几个注解类的作用:
@inject : 它有两个作用,一个是用来标记需要已被注入的变量(如 注入到 Computer中的mCpu 变量);二是用来标记被依赖对象的构造函数(CPU的构造函数)。Dagger 可以通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把实例构造出来,以此为被@Inject标记了的变量提供依赖。
@Module : 用于标记提供依赖的类(可以理解为仓库类,工厂类)。上面说到@Inject是用来标记被依赖对象的构造函数,而@Module的作用相当于 把这些被依赖对象统一收集到@Module标记的类中进行实例化。比如被依赖对象是在第三方库中的,我们是无法对它的构造函数做@Inject标记的,又或者说这个被依赖对象的构造函数是有参数的,那这个参数我们从哪里传入?这时@Module就可以解决这个问题。
@Provides : 用于标记@Module标记的类中的方法,该方法是在把被依赖对象注入使用时会调用。它的返回值就是被依赖对象。
@Component : 用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被Compent标注的结构在编译时会生成该接口的实现类(如被@Component标注的接口为 ComputeComponent,那么编译期间生成的实现类名为DaggerComputerComponent),那么你可以通过调用这个实现类的方法完成注入。
@Qulifer : 用于自定义注解。被依赖对象被注入到依赖对象中时,依赖对象对被依赖对象的创建过程是不关心的。但是@Provide提供的被依赖对象是根据方法返回值类型来识别的。那若果被依赖对象的创建过程有两种,那依赖对象需要的是哪一种呐?比如上面的例子,把Cpu注入到电脑的过程,电脑是不关心Cpu的生产过程的,但是cpu生成时有Intel和Amd两种可选。那电脑怎么去拿到想要的那种Cpu呢?
这时使用@Qulifer来定义自己的注解,然后通过自定义注解去标注提供依赖的方法和依赖需求方,这样就可以精准地拿到所需要的被依赖实例。
@Scope: 同样用于自定义注解,可以通过@Scope自定义的注解来限定注解作用于,实现局部的单例。
@Singleton : @Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提供全局单例,是否能提供全局单例还要取决于对应的Component是否为一个全局对象。
说了那么多,是不是一脸懵逼。伟大的XXX说过,没有经过实践的概念都是虾扯蛋。好 ,下面我们循序渐进地看看它的使用:
栗子1:
电脑 依赖 Cpu。 那需要把Cpu注入到 电脑中。
被依赖对象 CPU :
public class CPU {
//使用@Inject标记 被依赖对象 Cpu的构造方法。 这是注入时就会通过@Inject找到该构造函数并把实例构造出来
@Inject
public CPU () {
}
public void run() {
Log.d("======", "Intel CPU运行了");
}
}
桥梁(rebuild之后会生成实现类,DaggerComputerComponent):
@Component
public interface ComputerComponent {
void inject(Computer computer);
}
依赖对象:
public class Computer {
//注入被依赖对象
@Inject
Intel_CPU mCPU;
public Computer() {
//把依赖使用方传入到桥梁中
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
上面就是Dagger2的简单使用。
栗子2
这时问题来了。上面的@Inject是标记CPU类的无参构造函数的,那现在CPU的构造函数需要传入一个品牌的参数呢?这参数从哪里传进来。还有上面的@Inject是直接对CPU类进行操作的,那这时如果这个类是在类库中的,那这时是修改不了的,也就无法使用@Inject对其进行标记了。这时@Module和@Provide就出现了。
被依赖对象 CPU :
public class CPU {
private String brand;
//这时构造函数已不需要使用@Inject来标记
public CPU(String brand) {
this.brand = brand;
}
public void run() {
Log.d("======", brand + " CPU运行了");
}
}
被依赖对象的仓库类,被@Module标记:
@Module
public class CpuModule {
//在此处实例化被依赖对象,并通过@Provide提供出去
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
桥梁:
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
注入:
public class Computer {
@Inject
CPU mCPU;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
输出:
栗子3
现在问题又来了,假如CPU有两个牌子,Intel的和AMD,那实例化时也就有传入Indel和Amd两种。因为@Provide标记的方法也就仅仅知道其是CPU这个类型,并不知道它是Intel还是AMD的。这时就用到@Qulifier。
首先要使用@Qualifier自定义两个注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifilerIntel {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifilerAmd {
}
被依赖对象 CPU:
public class CPU {
private String brand;
public CPU(String brand) {
this.brand = brand;
}
public void run() {
Log.d("======", brand + " CPU运行了");
}
}
CPUModule 类:
@Module
public class CpuModule {
// 使用 @QualifilerIntel 标注
@QualifilerIntel
@Provides
public CPU provideIntelCPU() {
return new CPU("Intel");
}
// 使用 @QualifilerAmd标注区分
@QualifilerAmd
@Provides
public CPU provideAmdCPU() {
return new CPU("Amd");
}
}
桥梁
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
注入:
public class Computer {
//使用 @QualifilerIntel标记拿到指定牌子的CPU
@QualifilerIntel
@Inject
CPU mCPU;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
输出:
栗子4: @Scope
在上面的例子2中,我们对CPU和Computer类修改下:
public class CPU {
private String brand;
public CPU(String brand) {
this.brand = brand;
Log.d("======", "Create CPU");
}
public void run() {
Log.d("======", brand + " CPU运行了");
}
}
public class Computer {
@Inject
CPU mCPU1;
@Inject
CPU mCPU2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU1.run();
mCPU2.run();
}
}
输出如下,可以看到,注入两个变量,边创建两次CPU对象:
那现在我要这个CPU是单例的,不管注入多少次,它只创建一次,这是就用到了@Scope注解。
@Scope是通过限定作用域,实现局部单例的。
首先我们需要通过@Scope定义一个CPUScope注解:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CPUScope {
}
然后我们拿这个注解去标记依赖提供方的仓库类CPUModule:
@Module
public class CpuModule {
@CPUScope
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
同时还需要使用@Scope来标注注入器Component:
@CPUScope
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
依赖对象Computer:
public class Computer {
@Inject
CPU cpu1;
@Inject
CPU cpu2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
cpu1.run();
cpu2.run();
}
}
被依赖对象CPU类:
public class CPU {
private String brand;
public CPU(String brand) {
this.brand = brand;
Log.d("======", "Create CPU");
}
public void run() {
Log.d("======", brand.hashCode() +"==" + brand + " CPU运行了");
}
}
调用:
Computer computer = new Computer();
computer.run();
输出如下,可以看到注入两个CPU,但是只执行了一个构造方法,证明只创建了一次Cpu实例,cpu1和cpu2为同一对象。
但是,这个单例仅仅是对当前实例化的computer这个对象生效,并不是全局单例的。假如我再实例化一个Computer:
Computer computer = new Computer();
computer.run();
Computer computer2 = new Computer();
computer2.run();
我们再来看看会输出是怎样:
看以看到单例没有在computer和computer2之间生效。那现在我需要全局单例,怎么办呐?先别急,这时我们肯定会疑问,为什么是只对当前依赖对象生效?那就回头看看这个Computer的类:
public class Computer {
@Inject
CPU cpu1;
@Inject
CPU cpu2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
cpu1.run();
cpu2.run();
}
}
里面关键代码就那行: DaggerComputerComponent.builder().build().inject(this);
上面的代码作用就是:通过Budiler模式,生成DaggerComputerComponent这个注入器,通过这个注入器把被依赖对象(CPU)注入到这个类中。既然上面说到两个Computer实例拿到的CPU并不是同一对象,也就是说这个注解器也不是 同一对象,那实现全局单例的思路是不是来了?我只需要保证这个注入器Compoment是唯一的就ok啦。那我只要把这Compoment的生成过程放到Application的onCreate中不就ok了吗?撸起袖子就是干。
栗子5 全局单例 : ApplicationComponent
定义ApplicationCompoment:
@CPUScope
@Component(modules = CpuModule.class)
public interface ApplicationCompoment {
void inject(Computer computer);
}
自定义Application,并且初始化ApplicationCompoment:
public class MApplication extends Application {
private static ApplicationCompoment mApplicationCompoment;
@Override
public void onCreate() {
super.onCreate();
mApplicationCompoment = DaggerApplicationCompoment.builder().cpuModule(new CpuModule()).build();
}
public static ApplicationCompoment getApplicationCompoment() {
return mApplicationCompoment;
}
}
然后Computer的使用如下,利用Application中的ApplicationComponent进行注入:
public class Computer {
@Inject
CPU cpu1;
@Inject
CPU cpu2;
public Computer() {
MApplication.getApplicationCompoment().inject(this);
}
public void run() {
cpu1.run();
cpu2.run();
}
}
调用:
Computer computer = new Computer();
computer.run();
Computer computer2 = new Computer();
computer2.run();
输出如下:
栗子6
@SingleTon 就是一个现成的被@Scope标注的注解,用来限定作用域。使用和上面的@CPUScope一样。
栗子7: include
当然,当你的Compoment可以注入多个Module的依赖对象时,可以使用花括号把多个Module.class 括起来,如以下:
@CPUScope
@Component(modules = {CpuModule.class, RamModule.class})
public interface ComputerComponent {
void inject(Computer computer);
}
当某个Module包含另外一个Module时,可以使用includes字段:
举个例子,现在有个需求,CPU包含高配置CPU,我想把高配置的Cpu集成到一个独立Module中,专门提供给高端客户,而原来的CpuModule也仍然可以使用这个高配置的Cpu。
那首先肯定是创建一个提供高端Cpu的Module : AndvancedModule
@Module
public class AdvancedCpuModule {
@Provides
AdvanedCPU provideCpu() {
return new AdvanedCPU("Indel i9");
}
}
然后在CpuModule 中使用includes 即可以包含AnvancedModule:
@Module(includes = AdvancedCpuModule.class)
public class CpuModule {
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
而当你的Module中提供的依赖对象需要参数进行实例化,而这个参数也是作为一个依赖对象存在,那么也可以使用includes解决:
如下,Disk 依赖于DiskBrand:
public class Disk {
private DiskBrand mDiskBrand;
public Disk(DiskBrand diskBrand) {
mDiskBrand = diskBrand;
}
public void run() {
Log.d("======", this.hashCode() + "==" + mDiskBrand.brand + " 硬盘运转了");
}
}
而DiskBrand也作为依赖对象存在:
@Module
public class DiskBrandModule {
@QualifilerA
@Provides
DiskBrand provideDiskBrand1() {
return new DiskBrand("三星");
}
@QualifilerB
@Provides
DiskBrand provideDiskBrand2() {
return new DiskBrand("希捷");
}
}
那么如下,Disk的提供方法的参数就可以使用DiskModule中提供的依赖对象DiskBrand:
@Module(includes = DiskBrandModule.class)
public class DiskModule {
@Provides
Disk provideDisk(@QualifilerAmd DiskBrand diskBrand) {
return new Disk(diskBrand);
}
}
注入:
public class Computer {
@Inject
Disk mDisk;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mDisk.run();
}
}
输出如下:
栗子8:dependencies
dependencies :用于component之间的依赖。如ApplicationComponent是全局的component,而其他的component需要用到ApplicationComponent的注入。那这时就可以使用dependencies,使得可以依赖于ApplicationComponent了。
被依赖对象: 显示器类 Display:
public class Display {
public int size;
public String brand;
public Display(int size, String brand) {
this.size = size;
this.brand = brand;
}
public void run() {
Log.d("======", this.hashCode() + "==" + size + "寸" + brand + "显示器");
}
}
被依赖对象提供方(仓库/工具类) DisplayModule:注意下面提供了两种Display 实例
@Module
public class DisplayModule {
@QualifilerA
@Singleton
@Provides
Display provideDisplayA() {
return new Display(27, "Dell");
}
@QualifilerB
@Singleton
@Provides
Display provideDisplayB() {
return new Display(22, "Philips");
}
}
被依赖的桥梁, ApplicationCpmponent:
@Singleton
@Component(modules = {DisplayModule.class})
public interface ApplicationCompoment {
//注意: 这里通过这种形式把DisplayModule 中用 @QualifilerA标记的那种 Display显示出来,而 @QualifilerB标记的那种 Display 并没有
@QualifilerA Display display();
}
真正使用的Component , ComputerComponent,可以看到其通过dependencies依赖了ApplicationComponent:
@Component(dependencies = ApplicationCompoment.class)
public interface ComputerComponent {
void inject(Computer computer);
}
然后在Computer中进行注入:
public class Computer {
@QualifilerA
@Inject
Display mDisplay;
//这里注入@QualifilerB 标注的Display,会编译不通过,因为ApplicationComponent没有显示提供出来。
/*@QualifilerB
@Inject
Display mDisplay1;*/
public Computer() {
// 通过 applicationCompoment()把ApplicationComponent关联
DaggerComputerComponent
.builder()
.applicationCompoment(MApplication.getApplicationCompoment())
.build()
.inject(this);
}
public void run() {
mDisplay.run();
}
}
调用:
Computer computer = new Computer();
computer.run();
打印:
源码分析
好了,说了那么多,肯定会产生疑问为什么可以这样用,这是怎么实现的。其实很简单,我们就去一探究竟。
我们就以例子2来说。
再来回顾下 : CpuModule:
@Module
public class CpuModule {
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
CPUComponent:
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
注入:
public class Computer {
@Inject
CPU mCPU;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
在我们写好ComputerComponent后,重新Rebuild之后会生成ComputerComponent的实现类DaggerComputerComponent。这里不会说Dagger根据注解生成 DaggerComputerComponent的原理是怎样的,主要是分析这个注入的是怎样一个流程:
我们直接去DaggerComputerComponent源码:
public final class DaggerComputerComponent implements ComputerComponent {
private CpuModule cpuModule;
private DaggerComputerComponent1(Builder builder) {
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static ComputerComponent create() {
return new Builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.cpuModule = builder.cpuModule;
}
@Override
public void inject(Computer computer) {
injectComputer(computer);
}
private Computer injectComputer(Computer instance) {
Computer_MembersInjector.injectMCPU(
instance, CpuModule_ProvideCPUFactory.proxyProvideCPU(cpuModule));
return instance;
}
public static final class Builder {
private CpuModule1 cpuModule;
private Builder() {}
public ComputerComponent build() {
//实例化CpuModule
if (cpuModule == null) {
this.cpuModule = new CpuModule();
}
return new DaggerComputerComponent(this);
}
public Builder cpuModule(CpuModule cpuModule) {
this.cpuModule = Preconditions.checkNotNull(cpuModule);
return this;
}
}
}
可以看到上面的代码是通过Builder模式生成DaggerComputerComponent实例和CpuModule实例。然后在inject()方法中调用 injectComputer(computer);
而在injectComputer(computer)这个方法中执行了
Computer_MembersInjector.injectMCPU(
instance, CpuModule1_ProvideCPUFactory.proxyProvideCPU(cpuModule));
我们先来来看看
CpuModule1_ProvideCPUFactory.proxyProvideCPU(cpuModule)这个里面做了什么:
public final class CpuModule_ProvideCPUFactory implements Factory<CPU> {
private final CpuModule module;
public CpuModule_ProvideCPUFactory(CpuModule module) {
this.module = module;
}
@Override
public CPU get() {
return provideInstance(module);
}
public static CPU provideInstance(CpuModule module) {
return proxyProvideCPU(module);
}
public static CpuModule_ProvideCPUFactory create(CpuModule module) {
return new CpuModule_ProvideCPUFactory(module);
}
public static CPU proxyProvideCPU(CpuModule instance) {
return Preconditions.checkNotNull(
instance.provideCPU(), "Cannot return null from a aaa@qq.com @Provides method");
}
}
可以看到其实就是执行了我们定义的CpuModule 的provideCPU()方法,去生成了CPU实例。而这个CPU的实例最终是通过上面的get()方法提供出去的。
好了,我们在回到上面的
Computer_MembersInjector.injectMCPU(
instance, CpuModule_ProvideCPUFactory.proxyProvideCPU(cpuModule));
看看Computer_MembersInjector.injectMCPU()做了什么:
public final class Computer_MembersInjector implements MembersInjector<Computer> {
private final Provider<CPU> mCPUProvider;
public Computer_MembersInjector(Provider<CPU> mCPUProvider) {
this.mCPUProvider = mCPUProvider;
}
public static MembersInjector<Computer> create(Provider<CPU> mCPUProvider) {
return new Computer_MembersInjector(mCPUProvider);
}
@Override
public void injectMembers(Computer instance) {
injectMCPU(instance, mCPUProvider.get());
}
public static void injectMCPU(Computer instance, CPU mCPU) {
instance.mCPU = mCPU;
}
}
咦,这不就是对Computer的mCpu变量进行赋值操作吗。综上所述,CpuModule生成的实例就已经传递给Computer对象了。
好了,终于说完了。若有哪里写得不对的地方,欢迎留言指出。
上一篇: 燕国实力并不强,为什么他能打败齐国?
下一篇: 开始做SEO的6个重要步骤分析