Dagger2 依赖的接力游戏(五):Qualifier和Scope使用

文接Dagger2 依赖的接力游戏(四)

PS: 本节的示例代码收录在项目的chapter5分支

经过前面的四篇文章,我们已经介绍过了依赖和依赖注入,以及演示了如何使用Inject、Module、Component、SubComponent注解,采用不同的方式来实现Engine到Car的依赖注入。到目前为止我们介绍的都是Dagger2里依赖关系的维护机制,即如何声明和使用:需求R、提供P、注入I、依赖D、容器C。

我们知道Dagger2是基于的类型来管理依赖关系的,但是有时候只有类型信息无法满足我们对依赖对象实例化精确控制的需求,比如我们有一个司机类Driver,他同一时间只能开一辆车,我们希望他每次获取的Car对象都是同一个实例。又比如我们想根据不同的需求场景,返回相同类型,但是不同配置的对象,比如买菜就用蒙迪欧,泡妞就用野马,它们都属于Car类(为了通用性,不依赖具体的汽车品牌)。Dagger2为我们提供了类型以外的机制,来帮我们实现这些需求。

使用Qualifier修饰

Dagger2为每一个类型,增加了一个编译时期的辨识维度:类型修饰。类型修饰使用@Qualifer修饰的自定义注解来实现,用于区别相同类型的对象创建。我们举例说明:

我们定义两个Engine类的修饰:

@Qualifier
public @interface QualifierEngineA {}

@Qualifier
public @interface QualifierEngineB {}

然后在EngineModule里根据类型,返回不同气缸数目的Engine对象:

@Module(subcomponents = CarComponent.class)
public class EngineModule {

    @Provides
    @QualifierEngineA
    Engine provideEngineA(){
        return new Engine(3);
    }

    @Provides
    @QualifierEngineB
    Engine provideEngineB(){
        return new Engine(5);
    }

}

在EngineComponent中同样要使用修饰来声明返回类型:

@Component(modules = EngineModule.class)
public interface EngineComponent {

    @QualifierEngineA
    Engine getEngineA();

    @QualifierEngineB
    Engine getEngineB();

    CarComponent.Builder carComponent();
}

为了使用不同的Engine,我们再使用同样的方法定义两个Car的修饰:

@Qualifier
public @interface QualifierCarA {}

@Qualifier
public @interface QualifierCarB {}

然后我们同样在CarModule里声明不同修饰类型使用不同的引擎类型:

@Module
public class CarModule {

    @Provides
    @QualifierCarA
    Car provideCarA(@QualifierEngineA Engine engine){
        return new Car(engine);
    }

    @Provides
    @QualifierCarB
    Car provideCarB(@QualifierEngineB Engine engine){
        return new Car(engine);
    }
}

注意,我们这里通过Engine参数的修饰类型,让CarA对应EngineA,CarB对应EngineB。接下来在CarComponent中声明两种修饰类型:

@Subcomponent(modules = CarModule.class)
public interface CarComponent {

    @QualifierCarA
    Car getCarA();

    @QualifierCarB
    Car getCarB();

    @Subcomponent.Builder
    interface Builder{

        Builder carModule(CarModule module);

        CarComponent build();

    }
}

Main方法里,我们创建两个不同类型的Car查看他们的气缸数目:

public class Main {
    public static void main(String[] args) {
        CarComponent carComponent = DaggerEngineComponent.builder()
                .engineModule(new EngineModule())
                .build()
                .carComponent()
                .carModule(new CarModule())
                .build();

        Car carA = carComponent.getCarA();
        Car carB = carComponent.getCarB();
        System.out.println("carA cylinderNumbers : " + carA.getEngine().getCylinderNumbers());
        System.out.println("carB cylinderNumbers : " + carB.getEngine().getCylinderNumbers());
    }
}

运行一下:

carA cylinderNumbers : 3
carB cylinderNumbers : 5

Process finished with exit code 0

说明我们使用修饰来区分相同类型的逻辑是正确的,我们看一下关键部分的源码:

    @Override
    public Car getCarA() {
      return CarModule_ProvideCarAFactory.proxyProvideCarA(
          carModule,
          EngineModule_ProvideEngineAFactory.proxyProvideEngineA(
              DaggerEngineComponent.this.engineModule));
    }

    @Override
    public Car getCarB() {
      return CarModule_ProvideCarBFactory.proxyProvideCarB(
          carModule,
          EngineModule_ProvideEngineBFactory.proxyProvideEngineB(
              DaggerEngineComponent.this.engineModule));
    }

可以看到Dagger2为Car和Engine分别生成了两组Proxy和Factory,并且getCarA方法调用的是proxyProvideEngineA,而getCarB方法调用的是proxyProvideEngineB,没有其他额外的任何信息。也就是说它是在编译时进行修饰匹配检查的,一旦编译完成,就绑定了具体的创建方法,然后和正常的类型使用没有任何区别了。

使用Scope作用域

Scope用于声明对象的作用域,在这作用域内,对象是一个单例。如果Component不支持该作用域,则无法使用包含该对象创建的模块,如果支持该作用域,那么Component只能创建一个该对象的实例,同时SubComponent无法继承和它拥有相同作用域的Component。

如果我们把程序一次运行的过程,即一个完整的生命周期,当做是一个平面。那么Component的这种性质,相当于将这个平面抽象出了垂直方向的层次,当我们使用SubComponent来继承一个Component的时候,Dagger2就要求它们不能拥有相同的Scope,即不能属于同一个垂直层次,以此来保证子组件这种使用方式的必要性。因为子组件可以使用父组件的依赖关系,假如在同一层的组件,也能使用子组件来实现,那么就无法保证组件之间的独立性,就会影响组件服务的软件对象的生命周期。

举例来说,假如我们有一个EngineActivity和CarActivity,如果我们将CarComponent设计为EngineComponent的子组件,那么在调用时,我们能够将EngineActivity里在用的EngineComponent实例,同时传给CarActivity来创建CarComponent,而后者就可以持有这个EngineComponent,进而持有EngineActivity,引起内存泄露。如果我们将它们设计成同一层的组件,那么CarComponent和EngineComponent就没有依赖关系,进而CarActivity和EngineActivity也就没有依赖关系,从根本上避免了泄露的可能。

接下来我们通过Scope类型,将CarA类型做成单例 ,而CarB保持不变。首先定义两个Scope:

@Scope
public @interface ScopeCarA {}

@Scope
public @interface ScopeCarB {}

在Module里我们只使用ScopeCarA,用CarB来做对比:

@Module
public class CarModule {

    @Provides
    @ScopeCarA
    @QualifierCarA
    Car provideCarA(@QualifierEngineA Engine engine){
        return new Car(engine);
    }

    @Provides
    @QualifierCarB
    Car provideCarB(@QualifierEngineB Engine engine){
        return new Car(engine);
    }
}

然后在Component里我们把这两个作用域都加上:

@ScopeCarA
@ScopeCarB
@Subcomponent(modules = CarModule.class)
public interface CarComponent {

    @QualifierCarA
    Car getCarA();

    @QualifierCarB
    Car getCarB();

    @Subcomponent.Builder
    interface Builder{

        Builder carModule(CarModule module);

        CarComponent build();

    }
}

然后我们再main方法里进行检验:

public class Main {
    public static void main(String[] args) {
        CarComponent carComponent = DaggerEngineComponent.builder()
                .engineModule(new EngineModule())
                .build()
                .carComponent()
                .carModule(new CarModule())
                .build();

        Car carA1 = carComponent.getCarA();
        Car carA2 = carComponent.getCarA();
        Car carA3 = carComponent.getCarA();
        System.out.println("carA1 : " + carA1 + "; cylinderNumbers : " + carA1.getEngine().getCylinderNumbers());
        System.out.println("carA2 : " + carA2 + "; cylinderNumbers : " + carA2.getEngine().getCylinderNumbers());
        System.out.println("carA3 : " + carA3 + "; cylinderNumbers : " + carA3.getEngine().getCylinderNumbers() + "\n");

        Car carB1 = carComponent.getCarB();
        Car carB2 = carComponent.getCarB();
        Car carB3 = carComponent.getCarB();
        System.out.println("carB1 : " + carB1 +  "; cylinderNumbers : " + carB1.getEngine().getCylinderNumbers());
        System.out.println("carB2 : " + carB2 +  "; cylinderNumbers : " + carB2.getEngine().getCylinderNumbers());
        System.out.println("carB3 : " + carB3 +  "; cylinderNumbers : " + carB3.getEngine().getCylinderNumbers());
    }
}

运行一下:

carA1 : com.example.dagger2.Car@1d44bcfa; cylinderNumbers : 3
carA2 : com.example.dagger2.Car@1d44bcfa; cylinderNumbers : 3
carA3 : com.example.dagger2.Car@1d44bcfa; cylinderNumbers : 3

carB1 : com.example.dagger2.Car@266474c2; cylinderNumbers : 5
carB2 : com.example.dagger2.Car@6f94fa3e; cylinderNumbers : 5
carB3 : com.example.dagger2.Car@5e481248; cylinderNumbers : 5

Process finished with exit code 0

可以看到所有的CarA修饰的都是同一个对象。

同样我们也通过关键代码来分析下实现的区别,我们先看CarB是如何创建的:

    @Override
    public Car getCarB() {
      return CarModule_ProvideCarBFactory.proxyProvideCarB(
          carModule,
          EngineModule_ProvideEngineBFactory.proxyProvideEngineB(
              DaggerEngineComponent.this.engineModule));
    }

后面的一长串是创建Engine用的,因为我们是基于上一个例子来写的,在这里我们又看到修饰匹配后的绑定关系。抛开这个逻辑,我们可以看到,这里的创建逻辑就是前面讲过的Factory + Module实例的方式。我们再看CarA是如何创建的:

    @Override
    public Car getCarA() {
      return provideCarAProvider.get();
    }

这里的provideCarAProvider是什么东西,哪来的呢?

   @SuppressWarnings("unchecked")
    private void initialize(final CarComponentBuilder builder) {
      this.provideCarAProvider =
          DoubleCheck.provider(
              CarModule_ProvideCarAFactory.create(
                  builder.carModule, DaggerEngineComponent.this.provideEngineAProvider));
      this.carModule = builder.carModule;
    }

它是DaggerCarComponent构造方法里,使用Builder对象初始化自己时创建的。我们可以看到,这里和CarB的区别在于多了一层DoubleCheck.provider,剩下也是经典的Factory + Module实例的模式。也就是说单例和非单例只多了这一层逻辑,我们看看这个是啥:

  public static 

, T> Provider provider(P delegate) { checkNotNull(delegate); if (delegate instanceof DoubleCheck) { /* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped * binding, we shouldn't cache the value again. */ return delegate; } return new DoubleCheck(delegate); }

这里的泛型定义有点眼花,我们一个一个来解释。

首先是泛型参数定义

, T>,这里定义两个泛型,P和T。P是Provider的子类。
然后是返回类型定义Provider
然后方法名provider和参数定义P。

也就是说它接受一个Provider子类,又返回一个Provider类型。我们看方法定义,它先检查了P是不是DoubleCheck实例,是则直接返回,否则用参数直接创建一个实例返回,DoubleCheck这个类型对外是不可见的。这是啥?装饰者模式!通过装饰者扩展原类型的功能,而保持原类型的定义和功能不变。所以DoubleCheck的核心也是Provider声明的T get()方法,我们看看它怎么实现的:

private static final Object UNINITIALIZED = new Object();

  @SuppressWarnings("unchecked") // cast only happens when result comes from the provider
  @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;
  }

这里的逻辑还是比较清晰的,首先是一个同步检查,如果未初始化,就调用真正的provider获取一个实例,并进行重入检查。如果初始化过了,就直接返回这个实例,也就是说我们的单例其实就是这么来的

我们再稍微看下重入检查的逻辑:

  public static Object reentrantCheck(Object currentInstance, Object newInstance) {
    boolean isReentrant = !(currentInstance == UNINITIALIZED
        // This check is needed for AndroidMode's implementation, which uses MemoizedSentinel types.
        || currentInstance instanceof MemoizedSentinel);

    if (isReentrant && currentInstance != newInstance) {
      throw new IllegalStateException("Scoped provider was invoked recursively returning "
          + "different results: " + currentInstance + " & " + newInstance + ". This is likely "
          + "due to a circular dependency.");
    }
    return newInstance;
  }

重入检查首先还是检查旧的对象是不是初始值,这里的初始值有两个选项了,一个是UNINITIALIZED我们在上面已经贴出来了。还有一个是MemoizedSentinel类型检查。它长这样:

public final class MemoizedSentinel {}

什么都不做,其实就是定义出来帮助初始化一个空实例的类,但是这里并没有用到。如果这两个检查都不符合,也就是初始化过了,就对比这两个对象的地址,一样的话就抛出异常。也就是说,如果它代理的provider也返回它的instance,说明经过多层修饰之后(这个类的设计是允许多重装饰的,还记得我们的DoubleCheck类型的检查是在外面做的么?),最终用于返回实例的还是它自己的对象,出现了循环依赖。

通过上面的对比,我们发现如果Component的Scope类型包含了供类型P(?)的Scope类型,那么在Component中就会为P(?)编译出来的providerFactory类,加上一层DoubleCheck装饰,通过这层装饰来提供实例的同步单一性。也就是说,这个Scope的匹配过程,也是编译时期的,编译之后Scope信息就丢弃了。那如果Component不包含P(?)的Scope类型呢?那在Component里是不能使用这个P(?)的,否则会报编译错误。

Singleton注解

在上面的示例中,我们创建了自己的Scope类型的注解,如果我们只想要单例效果而不需要与其他对象区分开作用域,我们可以使用dagger2提供的预定义的注解@Singleton。由于子组件的层次递增性,假如在较高的组件层级定义了@Singleton,那么在较低的层级里就将无法使用到这个对象(否则这个层级依赖就无法实现,会编译错误),所以Singleton一般用于创建基层的单例或者全局的单例。

我们增加一个getCarC方法,用Singleton来修饰。

@Module
public class CarModule {
    @Provides
    @QualifierCarC
    @Singleton
    Car provideCarC(Engine engine){
        return new Car(engine);
    }
}

同样在Component中要增加Singleton类型:

@Singleton
@Subcomponent(modules = CarModule.class)
public interface CarComponent {

    @QualifierCarC
    Car getCarC();

    @Subcomponent.Builder
    interface Builder{

        Builder carModule(CarModule module);

        CarComponent build();

    }
}

我们直接运行一下,Dagger2同样会为getCarC实现DoubleCheck代理的单例:

    @Override
    public Car getCarC() {
      return provideCarCProvider.get();
    }

    @SuppressWarnings("unchecked")
    private void initialize(final CarComponentBuilder builder) {
      this.provideCarCProvider =
          DoubleCheck.provider(
              CarModule_ProvideCarCFactory.create(
                  builder.carModule, DaggerEngineComponent.this.provideEngineProvider));
    }

说明它的效果和自定义的Scope是一样的。

Reusable注解

为了实现对象的复用,dagger2还提供了@Reusable注解,这个注解不会定义一个作用域,因此不是配套使用的,它只要对提供方法进行修饰就好。这个注解提供的复用,不是严格的同步单例,只是一个“尽量复用”逻辑。

我们看看代码,先在Module中标注提供方法:

    @Provides
    @QualifierCarD
    @Reusable
    Car provideCarD(Engine engine){
        return new Car(engine);
    }

Component中这么使用:

@Subcomponent(modules = CarModule.class)
public interface CarComponent {
    @QualifierCarD
    Car getCarD();

    @Subcomponent.Builder
    interface Builder{
        Builder carModule(CarModule module);
        CarComponent build();
    }
}

我们测试一下:

    private static void testCar(EngineComponent engineComponent) {

        final CarComponent carComponent = engineComponent.carComponent().carModule(new CarModule()).build();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Car carD = carComponent.getCarD();
                System.out.println("thread : " + Thread.currentThread().getName() + "; carD : " + carD);
            }
        };

        List threads = new ArrayList<>(10);

        for(int i = 0; i < 10; i ++){
            Thread thread = new Thread(runnable);
            threads.add(thread);
        }

        for(Thread thread : threads){
            thread.start();
        }
    }

查看结果是这样的,而且每次运行的结果都不一样:

thread : Thread-0; carD : com.example.dagger2.Car@2b6cbf12
thread : Thread-4; carD : com.example.dagger2.Car@18401f16
thread : Thread-1; carD : com.example.dagger2.Car@18401f16
thread : Thread-3; carD : com.example.dagger2.Car@219be5ed
thread : Thread-2; carD : com.example.dagger2.Car@6b32f723
thread : Thread-5; carD : com.example.dagger2.Car@18401f16
thread : Thread-6; carD : com.example.dagger2.Car@18401f16
thread : Thread-7; carD : com.example.dagger2.Car@18401f16
thread : Thread-8; carD : com.example.dagger2.Car@18401f16
thread : Thread-9; carD : com.example.dagger2.Car@18401f16

我们看下它的实现:

    private void initialize(final CarComponentBuilder builder) {
      this.provideCarDProvider =
          SingleCheck.provider(
              CarModule_ProvideCarDFactory.create(
                  builder.carModule, DaggerEngineComponent.this.provideEngineProvider));
    }

可以看到它是一个SingleCheck装饰的,不是DoubleCheck,我们看看SingleCheck的get方法实现:

  public T get() {
    Object local = instance;
    if (local == UNINITIALIZED) {
      // provider is volatile and might become null after the check, so retrieve the provider first
      Provider providerReference = provider;
      if (providerReference == null) {
        // The provider was null, so the instance must already be set
        local = instance;
      } else {
        local = providerReference.get();
        instance = local;

        // 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) local;
  }

它仅判断是否创建过了,如果创建过了直接返回,否则创建一个,然后把provider丢掉。这里是没有同步锁的,所以它是一个后续重用逻辑,而不是绝对的单例模式。

小结

在本篇中,我们介绍了通过Qualifier和Scope注解增加依赖的类型信息,并讲解了它们的作用和原理。结合Scope的生命周期的讨论,我们还简单说明了一下SubComponent的独立生命周期。到此为止,我们已经了解了Dagger2最核心的注解和功能了,基本上可以满足日常开发需求了。接下来我们会介绍@Bind声明的类型绑定、@BindInstance声明的实例绑定和@MultiBind声明的多重绑定机制,敬请期待。

你可能感兴趣的:(Dagger2 依赖的接力游戏(五):Qualifier和Scope使用)