Dagger2 | 三、进阶 - @Provides

2020-06-30  本文已影响0人  mrzhqiang

在上一章,@Component 将项目中的类实例直接 new 出来,注入给依赖的地方。
对于第三方库,没法用 @Inject 标记,但 Dagger2 告诉我们可以 提供依赖

3.1 提供依赖

根据 @ProvidesAPI 描述,我们需要用 @Module 标记一个类,然后使用 @Provides 标记返回类型。

3.1.1 结构重构

在尝试之前,我们需要做一点小小的结构重构:

-java
    -com.domain.project
        -account
            *Account
        -di
            *ActivityComponent
        *MainAcitvity

现在,我们专注于 com.domain.project.di 包,开始我们全新的 dependencies inject 之旅。

3.1.2 依赖模块

首先在 com.domian.project.di 包下,创建 AccoutModule.java 类:

import dagger.Module;

@Module final class AccountModule {
}

OOP 角度来设计,我们不准备对外暴露这个模块,并且不允许它被扩展。

然后添加提供者方法:

@Module final class AccountModule {
  @Provides Account provideAccount() {
    return new Account();
  }
}

通常我们使用 new 提供依赖,在更广阔的应用场景,Dagger2 还有妙招,暂且不提。

再来查看生成的内容:

import com.github.mrzhqiang.dagger2_example.account.Account;
import dagger.internal.Factory;
import dagger.internal.Preconditions;

@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class AccountModule_ProvideAccountFactory implements Factory<Account> {
  private final AccountModule module;

  public AccountModule_ProvideAccountFactory(AccountModule module) {
    this.module = module;
  }

  @Override
  public Account get() {
    return provideAccount(module);
  }

  public static AccountModule_ProvideAccountFactory create(AccountModule module) {
    return new AccountModule_ProvideAccountFactory(module);
  }

  public static Account provideAccount(AccountModule instance) {
    return Preconditions.checkNotNull(instance.provideAccount(), "Cannot return null from a non-@Nullable @Provides method");
  }
}

本质上还是一个 Factory<T> 的实现,只不过依赖来自于账户模块。

3.1.3 模块管理

因为组件是 Dagger2 的核心,所以模块将交给 @Component 管理:

@Component(modules = AccountModule.class)
public interface ActivityComponent {
  void inject(MainActivity activity);
}

编译后的内容:

import com.github.mrzhqiang.dagger2_example.MainActivity;
import com.github.mrzhqiang.dagger2_example.MainActivity_MembersInjector;
import dagger.internal.Preconditions;

@SuppressWarnings({
    "unchecked",
    "rawtypes"
})
public final class DaggerActivityComponent implements ActivityComponent {
  private final AccountModule accountModule;

  private DaggerActivityComponent(AccountModule accountModuleParam) {
    this.accountModule = accountModuleParam;
  }

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

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

  @Override
  public void inject(MainActivity activity) {
    injectMainActivity(activity);}

  private MainActivity injectMainActivity(MainActivity instance) {
    MainActivity_MembersInjector.injectAccount(instance, AccountModule_ProvideAccountFactory.provideAccount(accountModule));
    return instance;
  }

  public static final class Builder {
    private AccountModule accountModule;

    private Builder() {
    }

    public Builder accountModule(AccountModule accountModule) {
      this.accountModule = Preconditions.checkNotNull(accountModule);
      return this;
    }

    public ActivityComponent build() {
      if (accountModule == null) {
        this.accountModule = new AccountModule();
      }
      return new DaggerActivityComponent(accountModule);
    }
  }
}

之前的 new 实例代码不见了,转而用 提供依赖 的方式。

思考:默认情况下,建造者模式会自动创建模块实例,如果从外部提供的话,将直接使用外部提供的模块实例。在这种情况下,模块是否可扩展将变得十分重要,这也是从一开始禁止模块扩展的原因。但我们也可以在不修改原有代码的前提下,进行依赖的迁移,前提是我们 面向接口编程

    public ActivityComponent build() {
      if (accountModule == null) {
        this.accountModule = new AccountModule();
      }
      return new DaggerActivityComponent(accountModule);
    }

3.1.4 移除注入

现在,我们试着移除构造函数上的 @Inject,再编译一下:

消失的 Account 工厂类

可以看到 Account 的工厂类已经消失,现在它是一个纯粹的 POJO 类。

3.2 第三方依赖

前面的内容不能完整描述提供第三方依赖的过程,所以我们可以再试一次。

3.2.1 神奇的依赖库

在项目的 build.gradle 文件中,引入 jitpack.io 依赖库:

allprojects {
  repositories {
    google()
    jcenter()
    maven { url 'https://jitpack.io' }
  }
}

这是一个可以直接提供 Git 开源项目依赖的 maven 库,不需要经历漫长的 Maven 中央库审核、上传、同步以及发布的过程,在项目初期的快速开发阶段,它的帮助非常大。
当然,如果你通过 nexus3 创建自己的 Maven 库,那就忽略我所说的这句话。

3.2.2 添加依赖

app 模块的 build.gradle 文件中,加入内容:

  implementation 'com.github.mrzhqiang:security:v1.0'

说明一下,这个库是从 spring-security-crypto 扒到 Android 上来,用于信息的加密处理。

为了运行这个库,我们还需要在 JDK1.8 版本上编译:

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

3.2.3 提供依赖

好了,我们可以提供这个库的依赖给账户模块:

import cn.mrzhqiang.security.crypto.factory.PasswordEncoderFactories;
import cn.mrzhqiang.security.crypto.password.PasswordEncoder;
import com.github.mrzhqiang.dagger2_example.account.Account;
import dagger.Module;
import dagger.Provides;

@Module final class AccountModule {
  @Provides Account provideAccount() {
    return new Account();
  }

  @Provides PasswordEncoder providePasswordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  }
}

编译后,在 di 包下生成一个新的提供者工厂:

密码编码的提供者工厂

3.2.4 注入依赖

再注入到 MainActivity 并对密码进行加密处理:

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import cn.mrzhqiang.security.crypto.password.PasswordEncoder;
import com.github.mrzhqiang.dagger2_example.account.Account;
import com.github.mrzhqiang.dagger2_example.di.DaggerActivityComponent;
import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {

  @Inject Account account;
  @Inject PasswordEncoder passwordEncoder;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    DaggerActivityComponent.create().inject(this);

    TextView contentText = findViewById(R.id.content_text);
    String content = String.format("username: %s, password: %s, encodePassword: %s",
        account.username,
        account.password,
        passwordEncoder.encode(account.password));
    contentText.setText(content);
  }
}

3.3 运行

现在运行一下,看看效果:

看到了吧,即使是 123456 这样的密码,经过加密处理后,也是普通暴力破解望而却步的长度和复杂度。

当然,更说明依赖注入现在是正常生效,并且 @Provides 更符合日常开发使用。

3.4 总结

@Provides 把第三方库依赖接入 Dagger2 的管理中,并带来一个全新的概念——@Module 模块化。
我们可以设计 NetworkModuleDatabaseModuleSecurityModule 等等模块,它们可以完全独立,也可以彼此关联。
一旦模块分类完毕,后续的依赖管理就变得轻松简单。

下一章,我们要实现组件和依赖的单例模式,以及如何巧妙避免注入时的重复代码。

上一篇 下一篇

猜你喜欢

热点阅读