学习Dagger2笔记:【2】@Inject

目录

0. 前言
1. 依赖与注入
2. @Inject
3. @Module & @Provides
4. @Component
5. @Qualifier
6. Provider & Lazy
7. @Scope
8. 注入到Set和Map容器
9. Bind系列注解
10. dagger中依赖关系与继承关系
11. dagger.android

Kotlin中的注解

在开始讲解注解之前,先看下kotlin中的注解有何不同

我们知道以下kotlin代码:

class Test(var temp: Int)

转换为Java代码后是:

public final class Test {
   private int temp;

   public final int getTemp() {
      return this.temp;
   }

   public final void setTemp(int var1) {
      this.temp = var1;
   }

   public Test(int temp) {
      this.temp = temp;
   }
}

那么问题来了:

  • 如何给构造函数添加注解?
  • 如果代码为class Test(@Ann var temp: Int),那么@Ann这个注解是作用于哪里?(构造函数中的参数、变量、get或是set方法等)
  • 如何在kotlin中指定注解的目标?

构造函数添加注解

这个问题其实不难,研究过kotlin语法的应该都知道kotlin有constructor关键字,上述写法是省略掉此关键字之后的主构造函数的写法,如果要使用注解,则需要显示写出constructor关键字,如下:

class Test @Ann constructor(var temp: Int)

注解的默认目标

修改一下代码如下:

class Test constructor(@Ann var temp: Int) {
    @Ann var foo = "Cmd"
}

猜猜这两个@Ann注解分别在什么地方?

public final class Test {
   @NotNull
   private String foo;
   private int temp;

   /** @deprecated */
   // $FF: synthetic method
   @Ann
   public static void foo$annotations() {
   }
    
   /* 省略掉get/set方法 */

   public Test(@Ann int temp) {
      this.temp = temp;
      this.foo = "Cmd";
   }
}

kotlin中注解首先根据@Target选择目标,如果有多个指定目标都符合时,有如下优先级:

  1. param:即方法中的参数
  2. property:一个对java不可见的属性(如上foo$annotations
  3. filed:变量

所以在上述代码在构造函数中@Ann的目标时构造函数的入参,而foo前的@Ann目标则是属性

指定注解目标

对于自定义的注解,也许我们可以指定@Target限制注解目标范围,但对于三方提供的注解(例如javax.inject中的@Inject),怎样指定注解的目标呢?kotlin给出了一系列关键字来辅助:

class Test constructor(
    @set:Ann var i1: Int, // 注解在set方法上
    @param:Ann val i2: Int // 注解在方法参数上
) {
    @setparam:Ann var foo = "Cmd" // 注解在set方法的参数上
    @get:Ann val bar = "Mei" // 注解在get方法上
    @field:Ann val zoo = "Zoo" // 注解在后端变量上
}

常用的关键字就上面那些,猜猜上述代码转为Java代码后是怎样的?

/* 已省略掉一些不关心的地方 */
public final class Test {
   @Ann
   private final String zoo;

   public final void setFoo(@Ann @NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "");
      this.foo = var1;
   }

   @Ann
   public final String getBar() {
      return this.bar;
   }

   @Ann
   public final void setI1(int var1) {
      this.i1 = var1;
   }

   public Test(int i1, @Ann int i2) {
      this.i1 = i1;
      this.i2 = i2;
      this.foo = "Cmd";
      this.bar = "Mei";
      this.zoo = "Zoo";
   }
}

补充

最后再补充一个同一目标多个注解的写法:

class Test constructor(@set:[Ann Ann2] var temp: Int)

更多关于kotlin中使用注解的东西还请查阅官网

@Inject注解

@Injectjavax.inject中定义的注解,其可以作用于构造函数、变量和方法上,我们通过编译触发dagger的注解处理器,来看看它做了什么(kapt生成的代码通常在build/generated/source/kapt下)

注解在构造函数上

我们分别在CPUMemory的构造函数上添加注解,分别看看对于无参和有参构造函数,@Inject会生成什么

class CPU @Inject constructor() { /* ... */ }
class Memory @Inject constructor(private val size: Int) { /* ... */ }

编译后,生成了CPU_FactoryMemory_Factory两个新类,这里先看下CPU_Factory

public final class CPU_Factory implements Factory<CPU> {
  private static final CPU_Factory INSTANCE = new CPU_Factory(); // 饿汉单例

  @Override
  public CPU get() { // 生产一个CPU对象
    return new CPU();
  }

  public static CPU_Factory create() { // 目前此方法不是我们关注的重点
    return INSTANCE;
  }

  public static CPU newInstance() {
    return new CPU();
  }
}

正如其名Factory,这是一个工厂类,所生产的对象是CPU,提供了两种生产方法——非静态的get()和静态的newInstance();而此工厂又是一个饿汉单例模式,不过我们这里仅关注工厂模式。接下来看下其实现的Factory这个接口:

public interface Factory<T> extends Provider<T> {} // 这是dagger中定义的接口
public interface Provider<T> { // 这是javax中定义的接口
    T get();
}

Provider的是专门为注入器设计的接口,通过get方法返回一个可被注入的对象实例,对象类型为T;而Factory则是一个未被范围标识(unscoped)的Provider

什么是未被范围标识这里并不是重点,总之Factory就是工厂类的接口,而dagger对于有@Inject注解的构造函数的类,会根据被注解的构造函数生成一个实现了Factory接口的工厂类

顺便一提,同一个类中若有多个构造函数,则@Inject仅能注解其中一个,不然工厂类就不知道用哪一个构造函数来生产对象了。我们知道在kotlin中如果构造函数有参数默认值,会被编译成多个重载构造函数,所以**@Inject不能用于有参数默认值的构造函数上**,否则编译后@Inject就会作用于多个构造函数了

再看看对有参构造函数注解@Inject生成的Memory_Factory类:

public final class Memory_Factory implements Factory<Memory> {
  private final Provider<Integer> sizeProvider; // 注意这里也是一个Provider

  public Memory_Factory(Provider<Integer> sizeProvider) {
    this.sizeProvider = sizeProvider;
  }

  @Override
  public Memory get() {
    return new Memory(sizeProvider.get());
  }

  public static Memory_Factory create(Provider<Integer> sizeProvider) {
    return new Memory_Factory(sizeProvider);
  }

  public static Memory newInstance(int size) {
    return new Memory(size);
  }
}

因为构造Memory需要依赖一个Integer对象,所以这个工厂不再是单例,并且有了一个Provider作为成员变量提供构造Memory时所需的参数,总之大同小异,其本质也就是生产Memory的工厂类

同理我们可以给Computer的构造函数也加上@Inject,并且可以改写一下我们的Injector类了,如下:

object CaseActivityInjector {
    fun inject(target: CaseActivity) {
        target.computer = Computer_Factory.newInstance("Windows", 6666).apply {
            cpu = CPU_Factory.newInstance()
            memory = Memory_Factory.newInstance(8192)
        }
    }
}

注解在变量和方法上

为了方便对比注解在变量和方法上的区别,此刻我们有新的需求,要求显示Computer信息前,还要显示当前页面的生成时间,我们在Activity中添加@Inject注解,如下:

class CaseActivity : AppCompatActivity() {
    @field:Inject
    lateinit var computer: Computer
    @set:Inject
    lateinit var timestamp: Date
    /* ... */
}

编译后可以发现dagger又生成了新的类——CaseActivity_MembersInjector

public final class CaseActivity_MembersInjector implements MembersInjector<CaseActivity> {
  // Provider上面说到过,是可以提供对象实例的接口
  private final Provider<Computer> computerProvider;
  private final Provider<Date> p0Provider;

  public CaseActivity_MembersInjector(
      Provider<Computer> computerProvider, Provider<Date> p0Provider) {
    this.computerProvider = computerProvider;
    this.p0Provider = p0Provider;
  }

  public static MembersInjector<CaseActivity> create(
      Provider<Computer> computerProvider, Provider<Date> p0Provider) {
    return new CaseActivity_MembersInjector(computerProvider, p0Provider);
  }

  @Override
  public void injectMembers(CaseActivity instance) {
    injectComputer(instance, computerProvider.get());
    injectSetTimestamp(instance, p0Provider.get());
  }

  public static void injectComputer(CaseActivity instance, Computer computer) {
    instance.computer = computer;
  }

  public static void injectSetTimestamp(CaseActivity instance, Date p0) {
    instance.setTimestamp(p0);
  }
}

很明显,这个类的作用和我们自己写的Injector一样,是一个注入器类,将目标类(Activity)的依赖(computer、timestamp)注入到目类中;提供了两种注入方法——非静态的injectMembers()和静态的injectComputer() & injecterTimestamp()

这里注意一下@Inject注解在变量和方法上的区别:前者的注入方式是直接对被@Inject注解的成员变量赋值;而后者的注入方式则是调用被@Inject注解的方法,即实际的注入逻辑还是交给目标类(这里说明一下,@Inject注释的方法可以没有参数也可以有多个参数

接下来看下上面CaseActivity_MenbersInjector实现的接口MenebersInjector

public interface MembersInjector<T> { // dagger中定义的接口
      void injectMembers(T instance);
}

这个接口就是用作将依赖注入到目标类的变量和方法中的,目标类的类型为T,调用injectMembers()方法以执行依赖注入

再次根据@Inject生成的工厂和注入器来改写我们的代码,不过这里注意不能再使用Injector这个名字了,因为注入器已经被dagger生成了,我们现在需要做的是使用注入器将工厂生产的对象注入到目标类中,那么就不妨叫做Bridge吧(为什么这么叫之后会说明):

object CaseActivityBridge {
    fun inject(target: CaseActivity) {
        // 创建依赖
        val computer = Computer_Factory.newInstance("Windows", 6666).apply {
            cpu = CPU_Factory.newInstance()
            memory = Memory_Factory.newInstance(8192)
        }
        val timestamp = Date(System.currentTimeMillis())
        // 使用注入器注入
        CaseActivity_MembersInjector.injectComputer(target, computer)
        CaseActivity_MembersInjector.injectSetTimestamp(target, timestamp)
    }
}

/* CaseActivity的onCreate方法 */
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_case)

    CaseActivityBridge.inject(this)

    show()
}

桥接模式

桥接模式是将多个维度的抽象与实现连接起来,这里和标准的桥接模式我觉得就只差一层抽象而已,这里更多关注的是Activity这个维度和注入器或工厂生产的对象实例这个维度连接起来,起到一个“桥梁”的作用。所以我将上面那个类称为Bridge,这个桥梁的具体桥接方式是通过注入器将对象实例注入到目标类Activity中(另一个角度来看,桥接其实是一种实现组合结构的方式)

总结

  • @Inject注解在构造函数上时,会根据被注解的构造函数生成该类的工厂类
  • @Inject注解在变量和方法上时,表示这些变量和方法需要被注入,会生成相应的注入器

如果被依赖对象只能通过@Inject注解构造函数生成工厂类的话,未免局限性太大了,比如被依赖对象是三方库中的对象,难道要修改库中源代码再编译吗?那系统对象呢?dagger当然也想到了这点

你可能感兴趣的:(Android)