出来混迟早要还的,技术债Dagger2:基础篇

4,730 阅读6分钟

前言

年前架构组的大佬们,分享了一个内容:如何让App Bundle支持Dagger2。

PS:关于App Bundle暂时不是本篇内容要讲的

会议就如何在App Bundle中高效的使用Dagger2展开了激烈的讨论,xxx表示应加强团队技术建设,规范Dagger2的使用...

我tm都没用过Dagger2,我是谁?我在哪?我都在听些什么?

正文

一、为什么需要依赖注入

个人觉得,开始一个新技术的学习。前提是弄清楚这个技术会为我们解决什么样的问题、痛点。 拿Dagger来说,它解决了什么呢?

对于我们来说,随着项目的越来越大,类爆炸,代码量激增的情况便会显现出来。如果我们某些类与某些类直接存在依赖关系的话,我们就会发现大量的new,导致了我们的类与类之间耦合的极为严重,进而我们的维护也变得更加复杂。

此时,依赖注入的思想便逐步被提上章程。用注入的方式,替代原本需要内部new的方式。而Dagger就是依赖注入框架之一,因其编译期生成代码(无性能损失),依赖关系代码透明的特点迅速走红~~

OK,不扯别的了,开始。

二、基础用法

2.1、入门注解

  • @Module和@Provides:定义提供依赖关系的类和方法
  • @Inject:需要的依赖。可以在构造函数,字段或方法上使用
  • @Component:用于构建将所有依赖关系连接在一起的接口

2.2、入门demo

Dagger2开始前,我们想用一个普通的demo。背景来自于《权利的游戏》,代码来源:Dagger 2 for Android Beginners — Dagger 2 part1

PS:访问此网站需要科学上网...

public class BattleOfBastards {

    public static void main(String[] args){
        // 关于IronBank、Allies、Starks、Boltons因为篇幅原因就不展开了,就是普通的类而已。
        IronBank bank = new IronBank();
        Allies allies = new Allies(bank);
        Starks starks = new Starks(allies, bank);
        Boltons boltons = new Boltons(allies, bank);

        War war = new War(starks, boltons);
        war.prepare();
        war.report();
    }
}

我们可以看到,想要运行这个demo,我们需要new出很多需要的类。可见当项目逐渐增大,这个类的维护难度可想而知。

接下来我们看一下用Dagger2进行改造:

public class Starks implements House {
    @Inject
    public Starks(){}

    @Override
    public void prepareForWar() {
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }

    @Override
    public void reportForWar() {
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}
// Boltons类同上

public class War {
    private Starks starks;
    private Boltons boltons;

    @Inject
    public War(Starks starks, Boltons bolton){
        this.starks = starks;
        this.boltons = bolton;
    }

    public void prepare(){
        starks.prepareForWar();
        boltons.prepareForWar();
    }

    public void report(){
        starks.reportForWar();
        boltons.reportForWar();
    }
}

@Component
interface BattleComponent {
    War getWar();
}

此时我们需要build一下,我们需要通过编译,让Dagger2帮我们生成我们所需要的类:BattleComponent,编译成功,我们就可以改造main()啦。

public class BattleOfBastards {
    public static void main(String[] args){
        BattleComponent component = DaggerBattleComponent.create();
        War war = component.getWar();
        war.prepare();
        war.report();
    }
}

很明显能够看出来,BattleComponent为我们承受了成吨的伤害。那么就让我们看一看BattleComponent的源码:

public final class DaggerBattleComponent implements BattleComponent {
  private DaggerBattleComponent(Builder builder) {}

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

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

  @Override
  public War getWar() {
    return new War(new Starks(), new Boltons());
  }

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

    public BattleComponent build() {
      return new DaggerBattleComponent(this);
    }
  }
}

这里我们可以看到,它实现了BattleComponent接口,并且getWar()是通过new War()的方式提供War的实例,这是因为,我们@Inject了War的构造方法,new Starks(), new Boltons()也是同样的道理。

2.3、@Module和@Provide注释

这里我们因为需要获取War的实例,在@Component了一个getWar()方法。那一定会有朋友问,如果我想外部获取Starks,是不是也可以定义一个getStarks()?那还用问吗,当然可以!

@Component
interface BattleComponent {
    War getWar();
    Starks getStarks();
    Boltons getBoltons();
}

StarksBoltons中的@Inject不变。这时我们重新Build一下,再看一看DaggerBattleComponent:

public final class DaggerBattleComponent implements BattleComponent {
  // 省略相同内容
  @Override
  public War getWar() {
    return new War(getStarks(), getBoltons());
  }

  @Override
  public Starks getStarks() {
    return new Starks();
  }

  @Override
  public Boltons getBoltons() {
    return new Boltons();
  }
  // 省略相同内容
}

这里自动实现的getStarks()是采用了new Starks();的方式,为什么是new,是因为我们@Inject注解到了Starks的构造方法上了。

如果这里我们没有@Inject会发生什么呢?

不用试了,最终会提示我们:需要提供@Inject或@Provides接口。既然提到了@Provides,那就让我们看一看它,不过说它之前,我们需要先看一看@Module:

@Module

@Module注释标记模块/类。例如,在Android中。我们可以有一个调用的模块ContextModule,该模块可以为其他类提供ApplicationContext和Context依赖。因此,我们需要将类标记ContextModule用@Module。

不理解,不要紧。一会结合代码再回过头就明白了。

@Provides

@Provides注释标记Module内部的方法,为外部提供了获取依赖的方式。例如,在上面提到的Android示例中,我们ContextModule使用@Module标记,我们需要使用@Provides标记提供ApplicationContext和Context实例的方法。

不理解,不要紧。一会结合代码再回过头就明白了。

接下来让我们把这俩个注解的内容也加入到BattleOfBastards之中:

先引入俩个全新的角色:钱和士兵

public class Cash {
    public Cash(){    //do something    }
}

public class Soldiers {
    public Soldiers(){  //do something    }
}

然后我们构造一个Module,用它来管理我们的钱和士兵:

@Module
public class BraavosModule {
    Cash cash;
    Soldiers soldiers;

    public BraavosModule(Cash cash, Soldiers soldiers){
        this.cash=cash;
        this.soldiers=soldiers;
    }

    @Provides
    Cash provideCash(){
        return cash;
    }

    @Provides
    Soldiers provideSoldiers(){
        return soldiers;
    }
}

这里@Module为我们增加了更多的依赖性,怎么让它生效呢?这样既可:

@Component(modules = BraavosModule.class)
interface BattleComponent {
    War getWar();
    
    Cash getCash();
    Soldiers getSoldiers();
}

然后我们可以这样使用我们的CashSoldiers

public class BattleOfBastards {
    public static void main(String[] args){
        BattleComponent component = DaggerBattleComponent
                .builder().braavosModule(new BraavosModule(new Cash(), new Soldiers())).build();
        War war = component.getWar();
        war.prepare();
        war.report();

        component.getCash();
        component.getSoldiers();
    }
}

我们component.getCash();所得到的Cash实例,是BraavosModule实例中provideCash()所提供的。也就是我们@Provides也注解的内容。不过@Provides是如何生成代码的?让我们一同看一下:

public final class DaggerBattleComponent implements BattleComponent {
    // 省略部分代码

    @Override
    public Cash getCash() {
        return BraavosModule_ProvideCashFactory.proxyProvideCash(braavosModule);
    }
}

// BraavosModule_ProvideCashFactory
public final class BraavosModule_ProvideCashFactory implements Factory<Cash> {
  private final BraavosModule module;

  public BraavosModule_ProvideCashFactory(BraavosModule module) {
    this.module = module;
  }

  // 省略部分无用内容
  public static Cash proxyProvideCash(BraavosModule instance) {
    return Preconditions.checkNotNull(
        instance.provideCash(), "Cannot return null from a non-@Nullable @Provides method");
  }
}

从上述生成的代码可以看到,我们调用getCash()时,实际上是调用了BraavosModule_ProvideCashFactory实例的proxyProvideCash(BraavosModule instance),这个方法最终调用instance.provideCash()返回我们@Provide的内容。

尾声

没想到光写基础的内容,就已经涉及到了这么多的内容。既然如果那么本篇内容就先止步于此,关于Android篇的内容,就放在下一篇文章中吧!

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身