Dagger2

什么是Dagger2

在我看来Dagger2是一个又高级又低级的的框架,在Java开发中,对象无处不在,无论是从初级开发,还是高级架构师,都离不开Objec object =new Objec();,每个java程序员每天都需要new 很多对象,为什么简简单单的new对象,却被Dagger2搞得如此复杂呢?

什么是依赖注入

在了解Dagge2之前,我们先简单了解一下,什么是依赖注入。当我们有一个Person类 需要去实例化,通常手段都是Computer computer = new Computer(), 如果 Computer 中需要依赖一个CPU的实例,一般情况下都是通过构造传入new Computer(new CPU),或者 set 方法 传入,这其实就是依赖注入最常见的两种方式。Dagger2 就是通过注解+JavaPoet等手段,在编译期间,动态生成(通过module)的方式提供依赖提供方,并将通过容器 将需求方进行赋值绑定。
在这里插入图片描述

Dagger2-基础

基础介绍

围绕着上面的问题,我们尝试使用Dagger来试一下效果,并了解下其中原理

@Inject

自动生成提供当前实例的工厂类 主要注释在两个地方(构造函数和全局变量)
在这里插入图片描述

  • 空构造函数:

被注解的类,Dagger会自动帮我们实例化对象,也就是 帮助我们做了new Computer()的动作,那让我们试一下,被@Inject注释的类会发生什么变化。

//CPU类,@Inject 后,Rebuid一下项目
class CPU {
    fun getCpuName() = "I9-10900X"
}

上面讲过 ,Dagger2会在编译期生成代码,Rebuid一下项目,我们就会看到Dagger帮我们生成的代码,让我们查看一下

  //省略..
  public final class CPU_Factory implements Factory<CPU> {
    @Override
    public CPU get() {
      return newInstance();
    }
    public static CPU_Factory create() {
      return InstanceHolder.INSTANCE;
    }
    public static CPU newInstance() {
      return new CPU();
    }
      private static final CPU_Factory INSTANCE = new CPU_Factory();
    }
  }

分析下生成的CPU_Factory类 首先不可修改 实现Factory接口,重写get()方法,主要就是做了两件事:

  • 重写get方法,返回我们@Inject 构造的对象,其实就是帮我们new 了一个 Person()

  • 生成一个CPU_Factory的单例(注意,是Phone_Factory,而不是Person单例)。

    那我们是不是可以通过CPU_Factory.create().get 就能拿到CPU的实例了呢,确实可以,但这样做完全没意义,直接new 不香么,没人会傻到这么做,我们记下来继续看下带参构造。

    • 带参构造:
    class Computer @Inject constructor(var cpu: CPU) {
        var phoneName = "Mac"
    }
    

    Rebuild一下项目

    public final class Computer_Factory implements Factory<Computer> {
      private final Provider<CPU> cpuProvider;
    
      public Computer_Factory(Provider<CPU> cpuProvider) {
        this.cpuProvider = cpuProvider;
      }
    
      @Override
      public Computer get() {
        return newInstance(cpuProvider.get());
      }
    
      public static Computer_Factory create(Provider<CPU> cpuProvider) {
        return new Computer_Factory(cpuProvider);
      }
    
      public static Computer newInstance(CPU cpu) {
        return new Computer(cpu);
      }
    }
    

    对比无参构造发现,你会发现一个区别:

    • 1、空参的Factory类是单例,而带参的Factory会需要一个Provider<参数> 作为入参
    • 2、Computer newInstance() 的时候需要一个参数,而这个参数,则是通过CPU_Factory.get()获取到的,其实这时候就关联起来了,这样我们就通过2、Computer_Factory.get()方法拿到了一个带着CPU对象的Computer引用。

    但这还是不够,因为没人会这样用,简直就是多此一举,接下来我们配合其他注解,继续完善代码。

    • 成员变量:

    在开发的过程中,并不是所有类都有构造,例如Android中,常见的Activity,全部是通过系统源码创建,那我们如何像Activity中注入所需要的对象呢,Dagger 为我们提供了一种方法,将@Inject 注解在成员变量上,通过容器类,将依赖方与需求方进行绑定。

    我们依旧将CPU注入给Computer

    1、声明容器

    //@Component下面会说到用法
    @Component
    interface ComputerComponent {
        fun inject(computer: Computer)
    }
    

    2、调用并注入

    class Computer {
        @Inject
        lateinit var cpu: CPU
    
        init {
            DaggerComputerComponent.create().inject(this)
        }
    }
    
    

    rebuild项目生成代码

    容器接口暂时省略…
    下面讲到在细说

    根据依赖需求方 Person 中 被@Inject修饰的变量 生成此类

  public final class Computer_MembersInjector implements MembersInjector<Computer> {
    private final Provider<CPU> cpuProvider;

    private final Provider<Disk> diskProvider;
    // 1
    public Computer_MembersInjector(Provider<CPU> cpuProvider, Provider<Disk> diskProvider) {
      this.cpuProvider = cpuProvider;
      this.diskProvider = diskProvider;
    }

    public static MembersInjector<Computer> create(Provider<CPU> cpuProvider,
        Provider<Disk> diskProvider) {
      return new Computer_MembersInjector(cpuProvider, diskProvider);
    }
    //2
    @Override
    public void injectMembers(Computer instance) {
      injectCpu(instance, cpuProvider.get());
      injectDisk(instance, diskProvider.get());
    }
    //3
    @InjectedFieldSignature("com.gx.task.di.demo.Computer.cpu")
    public static void injectCpu(Computer instance, CPU cpu) {
      instance.cpu = cpu;
    }

    @InjectedFieldSignature("com.gx.task.di.demo.Computer.disk")
    public static void injectDisk(Computer instance, Disk disk) {
      instance.disk = disk;
    }
  }

我们继续分析源码
来看被我们注释的成员变量:
- 1、Dagger检测到 Person类中有变量存在@Inject注释,自动根据Computer(需求方的名字)+_MembersInjector 实现 MembersInjector生成一个类。
- 2、构造根据被@Inject 注释变量的个数 传入多个Provider<T>
- 3、重写接口中injectMembers()方法,但暂时没用到
- 4、核心关键injectCpu()方法,将需求方和依赖方进行绑定。

最后我们总结一下@Inject注释:

  • 注解在构造:1、被注解的类,自动实现Provider<T>接口生成工厂类,通过实现接口中get()方法,主要用于提供注解类的对象。2、如果被注解的构造有参数,则会通过参数的Provider<T>get()方法,注入到需求方。
  • 注解在变量:2、被注解变量所在的类,自动实现MembersInjector<T>接口生成帮助类,通过实现接口中的injectMembers()方法,进行赋值绑定。
@Component

我们上面说过,整个依赖注入里面可以包括三个模块,被注入方,依赖提供方,容器。上面我们已经通过@Injected注释,讲了被注入方和提供方,接下来我们看看被@Component注释的接口。上面在注解成员变量的时候,我们有用过@Component注释的接口。让我们继续看下源码。
在这里插入图片描述

@Component相对来说比较简单,只能用在接口上

  • 简单对外提供实例
@Component
interface ComputerComponent {
    fun inject(computer: Computer)
}

依旧是Build一下项目

  // 1 调用DaggerComputerComponent.create().inject(this)
  public final class DaggerComputerComponent implements ComputerComponent {
    private DaggerComputerComponent() {

    }

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

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

    @Override
    public void inject(Computer computer) {
      injectComputer(computer);
    }

    private Computer injectComputer(Computer instance) {
      Computer_MembersInjector.injectCpu(instance, new CPU());
      Computer_MembersInjector.injectDisk(instance, new Disk());
      return instance;
    }

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

      public ComputerComponent build() {
        return new DaggerComputerComponent();
      }
    }
  }

分析下容器代码:

  • 1、被@Component 注释的接口 会自动以Dagger+接口名生成实现类,
  • 2、通过一个Builder模式提供当容器类的实例(为什么用Builder模式后面会讲到)
  • 3、重写接口方法,根据传入的需求方,进行赋值绑定。

简单总结一下@Component 注释:

  • 只能用于接口上,会根据接口自动生成容器类,通过Builder绑定。
  • 会重写接口里面的方法,将需求方传入,与提供方进行赋值绑定。

在简单的使用过程中,有几个疑问

  • 被@Inject修饰构造,所生产的Factory,其实并没有应用到
  • 为什么被@Component修饰的接口所生成的类 会通过Builder模式提供实例?

Dagger2-模块

在使用模块的时候,我们先考虑一个问题,我们上面的所讲的Demo中 依赖提供方都是可以
通过new去实例化的类,但是在实际开发过程中,我们有很多实例,是无法直接 new 出来的,例如,现在有一台电脑,只能有一块主板,一块CPU,所以主板和CPU均是通过单例提供,这个时候容器类,就无法帮我们new出来我们想要的实例,那主板如何给Computer提供依赖呢?这个时候就需要我们手动提供给容器接口,由他来进行绑定。

Dagger2给我们提供了两个新的注释 @Module 和 @Provides 配合@Component使用
在这里插入图片描述

模块基础使用

假设我们现在有一台电脑,需要两个配件,一个主板,一个CPU,但在整个电脑里,主板和CPU 都只能有一个。

//模块
@Module
class PartsModule() {
  //...
  @Provides
  fun provideCPU(): CPU {
      return CPU.create()!!
  }
}
//容器
@Component(modules = [PartsModule::class])
interface ComputerComponent {
    fun inject(computer: Computer)
}
class Computer() {
    @Inject
    lateinit var cpu: CPU

    @Inject
    lateinit var mainBoard: MainBoard

    @Inject
   lateinit var disk: Disk

    init {
      // 调用
       DaggerComputerComponent.builder()
       .computerModule(PartsModule()).build().inject(this)
    }
}

我们来看一下容器的源码

public final class DaggerComputerComponent implements ComputerComponent {
  private final PartsModule partsModule;

  private DaggerComputerComponent(PartsModule partsModuleParam) {
    this.partsModule = partsModuleParam;
  }

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

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

  @Override
  public void inject(Computer computer) {
    injectComputer(computer);
  }

  private Computer injectComputer(Computer instance) {
    Computer_MembersInjector.injectCpu(instance, PartsModule_ProvideCPUFactory.provideCPU(partsModule));
    Computer_MembersInjector.injectDisk(instance, new Disk());
    Computer_MembersInjector.injectMainBoard(instance, PartsModule_ProvideMainBoardFactory.provideMainBoard(partsModule));
    return instance;
  }

  public static final class Builder {
    private PartsModule partsModule;

    private Builder() {
    }

    public Builder partsModule(PartsModule partsModule) {
      this.partsModule = Preconditions.checkNotNull(partsModule);
      return this;
    }

    public ComputerComponent build() {
      if (partsModule == null) {
        this.partsModule = new PartsModule();
      }
      return new DaggerComputerComponent(partsModule);
    }
  }
}

大致流程和之前的容器代码一样

  • 1、被@Component 注释的接口 会自动以Dagger+接口名生成实现类,
  • 2、通过一个Builder模式提供当容器类的实例(为什么用Builder模式后面会讲到)
  • 3、重写接口方法,根据传入的需求方,进行赋值绑定。

差异化的是

  • 1、通过builder 注入 @Component 中的modules
  • 2、绑定时,可以直接new的类,直接 new ,不能 new的类, 通过module中提供的方法获取。

我们来看一下被module修饰的类会帮我们做哪些事情
被@module中@Provides修饰的方法,和最开始提及的被@Inject修饰的构造一样,均会实现Factory接口,重写get方法,用于提供实例。

public final class PartsModule_ProvideMainBoardFactory implements Factory<MainBoard> {
  private final PartsModule module;

  public PartsModule_ProvideMainBoardFactory(PartsModule module) {
    this.module = module;
  }
  @Override
  public MainBoard get() {
    return provideMainBoard(module);
  }

  public static PartsModule_ProvideMainBoardFactory create(PartsModule module) {
    return new PartsModule_ProvideMainBoardFactory(module);
  }

  public static MainBoard provideMainBoard(PartsModule instance) {
    return Preconditions.checkNotNullFromProvides(instance.provideMainBoard());
  }
}

总结:

  • 1、@module+@Provides 和 @Injcet在构造上的作用一样,均是为了提供实例,前者通过module类中的方法,获取实例。
  • 2、容器中的builder模式,就是为了应对多个module时,用于产生容器对象,协助绑定需求方。
模块传递参数

假设我们电脑中的部件,主板和显卡都需要供电,那我们在module中需要电的引用, 一般有几种方法 1、通过module的构造传入 2、使用@Provides提供实例。 我们围绕第二种来试一下,修改上面的代码。

一块需要供电的主板。

class MainBoard private constructor(var electric: Electric) {
    companion object {
        fun create(electric: Electric): MainBoard? {
            return InstanceHolder(electric).INSTANCE
        }

        private class InstanceHolder(electric: Electric) {
            val INSTANCE = MainBoard(electric)
        }
    }
}

module类

@Module
class PartsModule() {
    @Provides
    fun provideElectric() = Electric()

    @Provides
    fun provideMainBoard(electric: Electric): MainBoard {
        return MainBoard.create(electric)!!
    }
}

provideElectric()方法也是被@Provides修饰,所以会生成一个工厂类,用来提供Electric的实例。
接下里开下容器类,依旧是inject()方法。

  //省略....
  private Computer injectComputer(Computer instance) {
      Computer_MembersInjector.injectCpu(instance, PartsModule_ProvideCPUFactory.provideCPU(partsModule));
      Computer_MembersInjector.injectDisk(instance, new Disk());
      Computer_MembersInjector.injectMainBoard(instance, mainBoard());
      // Computer_MembersInjector.injectMainBoard(instance, PartsModule_ProvideMainBoardFactory.provideMainBoard(partsModule));
      return instance;
  }
  private MainBoard mainBoard() {
     return PartsModule_ProvideMainBoardFactory.provideMainBoard(partsModule, PartsModule_ProvideElectricFactory.provideElectric(partsModule));
 }

可以看出,在进行绑定的时候,injectMainBoard()方法里面,MainBoard实例获取的方式不一样了,但其根本依旧是通过PartsModule_ProvideMainBoardFactory.provideElectric()获取到,只是多了一个参数PartsModule_ProvideElectricFactory.provideElectric(partsModule),其实这个参数就是MainBorad所需的Electric实例。
至此,传递参数,我们也已经很清晰了。

Dagger2-范围(单例)

我们上面提到过的主板和CPU等部件,都是整个对象单例,但实际情况并不是所有电脑都用一个CPU,而是每一台电脑使用一个CPU,这个时候我们就需要优化我们的代码,使我们的单例仅针对这一台Computer,也就是模块的范围。

@Singleton
    //1 module
    @Singleton
    @Provides
    fun provideCPU(): CPU {
        return CPU()
    }

    //2 提供方
    @Singleton
    class CPU @Inject constructor()  {
      fun getCpuName() = "I9-10900X"
    }
    //3 容器
    @Singleton
    @Component(modules = [PartsModule::class])
    interface ComputerComponent {
        fun inject(computer: Computer)
    }

我们在外面new两个Computer的实例,看下日志

  class Computer(var context: Context) {
      @Inject
      lateinit var cpu: CPU

      @Inject
      lateinit var cpu1: CPU

      init {
          DaggerComputerComponent.builder().partsModule(PartsModule()).build().inject(this)
          Log.e("Computer cpu" ,cpu.toString())
          Log.e("Computer cpu1" ,cpu1.toString())
          // 第一个
          //cpu: [BaseActivity.kt, onCreate, 42]:com.gx.task.di.computer.CPU@ad51322
          //cpu1: [BaseActivity.kt, onCreate, 42]:com.gx.task.di.computer.CPU@ad51322

          //第二次输出
          //[BaseActivity.kt, onCreate, 43]:com.gx.task.di.computer.CPU@6d9b1b3
          //[BaseActivity.kt, onCreate, 43]:com.gx.task.di.computer.CPU@6d9b1b3
      }
  }

那Dagger是如何做到的,其实玄机就在容器类里面,我们只需要关注一下绑定时,依赖提供方的实例如何获取的,就知道Dagger是如何做到在module中单例。

  //1
  private Computer injectComputer(Computer instance) {
    Computer_MembersInjector.injectCpu(instance, provideCPUProvider.get());
    Computer_MembersInjector.injectCpu1(instance, provideCPUProvider.get());
    return instance;
  }
  //2
  private void initialize(final PartsModule partsModuleParam) {
    this.provideCPUProvider = DoubleCheck.provider(PartsModule_ProvideCPUFactory.create(partsModuleParam));
  }

  //3 DoubleCheck.class
  //核心单例
  @Override
  public T get() {
     Object result = instance;
     if (result == UNINITIALIZED) {
       synchronized (this) {
         result = instance;
         if (result == UNINITIALIZED) {
           result = provider.get();
           instance = reentrantCheck(instance, result);
           /* Null out the reference to the provider. We are never going to need it again, so we
            * can make it eligible for GC. */
           provider = null;
         }
       }
     }
     return (T) result;
   }

分析:

  • 1、在容器中绑定时,两个变量,都是通过provideCPUProvider.get()获取实例
  • 2、provideCPUProvider 根据 DoubleCheck.provider()代理所得,将PartsModule_ProvideCPUFactory实例转为DoubleCheck的实例,因为他们俩均实现了Provider接口
  • 3、最终provideCPUProvider已经被转换为DoubleCheck 所以调用的DoubleCheck重的get()方法,这里通过双重检查单例,返回CPU对象,这样就保证了容器中,所有的CPU都是同一个实例,所以注入到需求方,赋值绑定时,get()拿到的都是同一个实例。

Dagger2进阶-子组件

在实际项目中,为了方便管理,我们通常会将各个容器进行统一管理,这样有一个好处,父容器所提供的实例,子容器也可以用,并且子容器的生命周期是可控的。

上面我们只是讲了一台电脑,假如我们项目中 除了电脑 还有一部手机,这样的话,无论电脑还是手机都需要供电,那我们就需要把provideElectric()放在最顶层,
那我们就需要两个容器分别来提供 不同的实例
在这里插入图片描述

通过上面看出,在AppComponent中,通过AppMoudle 提供Electric实例,提供一个接口返回一个Electric实例。

使用


@Inject
lateinit var cpu: CPU
@Inject
lateinit var electric: Electric
@Inject
lateinit var otherPart: OtherPart
@Inject
lateinit var mainBoard: MainBoard

// 先构建App的容器
val appComponent = DaggerAppComponent.builder().appModule(AppModule()).build()
//由于 dependencies 关系,会在Buidler中 提供方法,注入appComponent
DaggerComputerComponent.builder().appComponent(appComponent).partsModule(PartsModule())

分析:DaggerAppComponent.class

public final class DaggerAppComponent implements AppComponent {
  private final AppModule appModule;

  private DaggerAppComponent(AppModule appModuleParam) {
    this.appModule = appModuleParam;
  }

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

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

  @Override
  public Electric provideElectric() {
    return AppModule_ProvideElectricFactory.provideElectric(appModule);
  }

  public static final class Builder {
    private AppModule appModule;

    private Builder() {
    }

    public Builder appModule(AppModule appModule) {
      this.appModule = Preconditions.checkNotNull(appModule);
      return this;
    }

    public AppComponent build() {
      if (appModule == null) {
        this.appModule = new AppModule();
      }
      return new DaggerAppComponent(appModule);
    }
  }
}

大致流程和之前的容器代码一样

  • 1、被@Component 注释的接口 会自动以Dagger+接口名生成实现类,
  • 2、重写接口方法,之前都是通过传入的需求方,进行赋值绑定,这次是通过一个带返回值的抽象方法方法,来获取实例。
  • 3、接口方法,会通过AppModule 进行回去实例。

分析:DaggerComputerComponent.class

public final class DaggerComputerComponent implements ComputerComponent {
  private final PartsModule partsModule;

  private final AppComponent appComponent;

  private DaggerComputerComponent(PartsModule partsModuleParam, AppComponent appComponentParam) {
    this.partsModule = partsModuleParam;
    this.appComponent = appComponentParam;
  }

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

  private MainBoard mainBoard() {
    return PartsModule_ProvideMainBoardFactory.provideMainBoard(partsModule, Preconditions.checkNotNullFromComponent(appComponent.provideElectric()));
  }

  @Override
  public void inject(Computer computer) {
    injectComputer(computer);
  }

  private Computer injectComputer(Computer instance) {
    Computer_MembersInjector.injectCpu(instance, PartsModule_ProvideCPUFactory.provideCPU(partsModule));
    Computer_MembersInjector.injectCpu1(instance, PartsModule_ProvideCPUFactory.provideCPU(partsModule));
    Computer_MembersInjector.injectElectric(instance, Preconditions.checkNotNullFromComponent(appComponent.provideElectric()));
    Computer_MembersInjector.injectOtherPart(instance, new OtherPart());
    Computer_MembersInjector.injectMainBoard(instance, mainBoard());
    return instance;
  }

  public static final class Builder {
    private PartsModule partsModule;

    private AppComponent appComponent;

    private Builder() {
    }

    public Builder partsModule(PartsModule partsModule) {
      this.partsModule = Preconditions.checkNotNull(partsModule);
      return this;
    }

    public Builder appComponent(AppComponent appComponent) {
      this.appComponent = Preconditions.checkNotNull(appComponent);
      return this;
    }

    public ComputerComponent build() {
      if (partsModule == null) {
        this.partsModule = new PartsModule();
      }
      Preconditions.checkBuilderRequirement(appComponent, AppComponent.class);
      return new DaggerComputerComponent(partsModule, appComponent);
    }
  }
}
  • 1、Builder 中新增 appComponent(),用于传入appComponent,
  • 2、问题在于,injectMainBoard()时,构造参数由何而来,是通过appComponent.provideElectric()获取,这样就和上面的关联起来了。
  • 3、如果在appComponent中 不提供实例,例如OtherPart(),那么injectOtherPart()则会帮忙new出一个实例,防止崩溃。

总结

我们在开头的时候说了Dagger又高级,又低级,在真的搞明白了Dagger后,你会发现Dagger会让项目中实例的依赖更加清晰,减少很多重复工作,但一个简简单单的new()就可以解决的问题,却被搞得如此复杂,还是仁者见仁。

有些朋友接手Dagger或者Hilt项目后,甚至找不到对象由何而来,希望这篇文章可以帮助到你。

参考资料

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值