Dagger2


https://segmentfault.com/a/1190000006665842


https://segmentfault.com/a/1190000006698289



什麼是依賴注入

依賴注入就是把下面這樣的代碼:

class A {
    public A() {
    
}

class B {
    A a;
    
    public B() {
        a = new A();
    }
}

class C {
    A a;
    B b;
    
    public C() {
        a = new A();
        b = new B();
        b.a = a;
    }

}
main() {
    C c = new C();
}

變成:

class A {
    A() {
    }    
}

class B {
    A a;
    
    B(A a) {
        this.a = a;
    }
}

class C {
    A a;
    B b;
    
    C(A a, B b) {
       this.a = a;
       this.b = b;
    }
}
main() {
    A a = new A();
    B b = new B(a);
    C c = new C(a, b);
}

這種把對象之間的依賴生成的責任交給外界的做法,叫做依賴注入。

如何更方便地進行依賴注入

我們有類和它們之間的依賴關係,便很自然地會用圖來表示這種狀態。如上例子所示,可用下面這樣一個圖來表示:

           +-----+
           |     |
    +----> |  A  | <----+
    |      |     |      |
    |      +-----+      |
    |                   |
    |                   |
+---+---+           +---+---+
|       |           |       |
|   B   | <---------+   C   |
|       |           |       |
+-------+           +-------+

箭頭表示依賴的對象。

我們想要這樣的一種依賴注入框架:當我們需要一個B對象時,框架按照依賴遍歷這個圖,生成A,然後將其注入B,最後返回一個已經生成好的B對象。大概是:

B b = Injector.create(B.class)

另外,如果要求A對象是單例(這裏不解釋什麼是單例)或對象的生成符合某種指定的規則,框架應自動識別並作出處理。

設計框架

我們面對兩個主要問題:如何表示依賴圖和如何生成對象。

依賴圖的表示

我們需定義一種聲明依賴的方法。可以用xml,json,甚至DSL來完成這個任務。這裏我們採用比較流行和簡便的註解(annotation)來表示依賴關係。

假設我們要的效果如下所示:

@dependence
class A {
}

@dependence(A.class)
class B {
}

@dependence({A.class, B.class})
class C {
}

可以看到,我們用@dependence註解來表示上面例圖中的箭頭。各個類之間的依賴關係十分清晰。

如果要求A是單例,我們可以這樣:

@singleton
@dependence()
class A {
}

對象生成

建立了依賴圖以後,需要通過某種方式生成我們需要的對象。我們希望是這樣的:

B b = Injector.create(B.class)

或者通過註解實現自動注入

class Main {
    @Inject
    B b;

    main() {
        Injector.inject(this);
        }
    }

Dagger2

我們來看一下Dagger2是如何實現上述兩個目標的。

依賴圖的表示

Dagger2中,是通過@Inject註解或者@Module和@Provide這兩個註解建立依賴圖,如下所示:

首先定義好類:

public class A {
    public A(){
    }
}

public class B {
    A a;

    public B(A a) {
        this.a = a;
    }
}

public class C {
    A a;
    B b;

    public C(A a, B b) {
        this.a = a;
        this.b = b;
    }
}

然後我們用第一種方法來聲明依賴:

public class A {
    @Inject
    public A() {
    }
}

public class B {
    A a;

    @Inject
    public B(A a) {
        this.a = a;
    }
}

public class C {
    A a;
    B b;

    @Inject
    public C(A a, B b) {
        this.a = a;
        this.b = b;
    }
}

可以看到我們爲每一個類的方法添加了@Inject聲明,表示該類是依賴圖中的一個節點。如果該初始化方法含有參數,那麼這些從參數也應是依賴圖中的節點。

第二種方法是通過一個module類來聲明依賴,如下所示:

@Module
public class ABCModule {
    @Provides
    public A provideA() {
        return new A();
    }

    @Provides
    public B provideB(A a) {
        return new B(a);
    }

    @Provides
    public C provideC(A a, B b) {
        return new C(a, b);
    }
}

@Module註解表示這個ABCModule的作用是聲明“依賴圖”的。@Provides註解表示當前方法的返回值是圖中的一個節點,方法的參數是依賴的對象,即前文中箭頭指向的目標。

再強調一次,Dagger要求圖中的每一個節點都要聲明,即每一個節點都要在module中有@Provides註解的方法或者@Inject註解的初始化方法。

可以看到第二種方式(module)無需修改原來的對象。爲了讓模塊儘量少地依賴第三方庫,一般採用第二種方式來聲明依賴圖。

對象生成

Dagger2中,從依賴圖中獲取對象需通過component。component是依賴圖和被注入對象之間的橋樑。如下所示:

@Component(module=ABCModule.class)
public interface ABCComponent {
    public A provideA();

    public B provideB();

    public C provideC();

    void inject(Activity mainActivity);
}

@Component註解表示ABCComponent這個接口是一個Component。Component的方法隱含着如下兩條規則:

  1. 不帶參數的方法爲“provider”方法,該方法的返回值是從依賴圖中取得的對象。如下所示(僞代碼):

class Main {
    
    C c;
    
    public void init() {
        c = Component.provideC();
    }
}
  1. 帶參數的方法,參數爲“注入對象”。通常於@Inject標籤同時使用。如下所示(僞代碼):

class Main {
    
    @Inject
    C c;
    
    public void init() {
        Component.inject(this);
    }
}

即調用Component.inject(foorbar)的時候,框架自動爲用@Inject標籤標註的屬性注入依賴。要求@Inject的屬性的類必須是依賴圖中的節點。

注意:component的方法必需至少符合以上兩條規則中的一條。

注意:provider方法的名字一般爲“provider”,inject方法的名字一般爲“inject”,但名字不影響這兩個方法的功能。

當Component聲明好以後,框架會在編譯時生成一個DaggerComponent名字的類,我們可以用它來實施依賴注入,如下所示:

ABCComponent abcComponent = DaggerABCComponent.create();
A a = abcComponent.provideA();
B b = abcComponent.provideB();
C c = abcComponent.provideC();

或者:

class Main {

    @Inject
    A a;
    @Inject
    B b;
    @Inject
    C c;

    public static void main() {
        ABCComponent abcComponent = DaggerABCComponent.create();
        abcComponent.inject(this);
    }
}

Component標籤的module屬性可以是一個數組,即一個Component實施多個module的注入。引入類D和DModule:

class D {
    public D() {
    }    
}

@Module
public class DModule {
    @Provides
    public D provideD() {
        return new D();
    }
}

修改ABCComponent,如下:

@Component(module={ABCModule.class, DModule.class})
public interface ABCComponent {
    public A provideA();

    public B provideB();

    public C provideC();

    public D provideD();

    void inject(Activity mainActivity);
}

如上即可實現D對象的注入。

Component之間的依賴

真正實施工程的時候,會將對象以功能分類。例如network相關,DB相關,Util相關的類集中在一起管理。Dagger2爲方便我們達到這一個目的,在component中引入了dependence這個功能。

例如我們有如下component

@Component(modules = DModule.class)
public interface DComponent {
    D provideD();
}

假設DComponent負責提供一個對象D。這種能力是項目無關的,我們把這個Component獨立出來。然後我們可以通過@Component的dependence屬性來爲其他Component引入DComponent的能力。例如:

@Component(modules = ABCModule.class, dependencies = DComponent.class)
public interface ABCComponent {
    A provideA();

    B provideB();

    C provideC();

    D provideD();

    void inject(Main main);
}

可以看到,聲明瞭dependencies=DComponent.class以後,provideD方法可以順利拿到D對象。inject方法也可以注入D對象。

public class Main {
    @Inject
    D d; // inject D by ABCComponent

    public Main() {
        DComponent dComponent = DaggerDComponent.create();
        D d1 = dComponent.provideD(); // inject D by DComponent

        ABCComponent abcComponent = DaggerABCComponent
                .builder()
                .dComponent(dComponent)
                .build();
        D d2 = abcComponent.provideD();
        abcComponent.inject(this);
    }
}

DComponent不知道ABCComponent的存在,故可以像普通Component那樣子使用。但在使用ABCComponent時,我們需要顯式地爲ABCComponent注入DComponent對象:

ABCComponent abcComponent = DaggerABCComponent
        .builder()
        .dComponent(dComponent)
        .build();

@Singleton

如上面例子所示,如果要求D對象爲單例,可以通過@Singleton註解來實現。首先我們需要在依賴圖中聲明對象是單例的:

@Module
public class DModule {
    @Provides
    @Singleton
    public D provideD() {
        return new D();
    }
}

DComponent接口也需要聲明:

@Singleton
@Component(modules = DModule.class)
public interface DComponent {
    D provideD();
}

如此,當我們注入D對象時,可保證每次注入的是同一個D對象:

DComponent dComponent = DaggerDComponent.create();
D d1 = dComponent.provideD(); 
D d2 = dComponent.provideD(); 

// d1 == d2

上一篇文章介紹了Dagger2的基本用法,這篇文章主要說一下Dagger2中@Scope的用法和原理。

上一篇文章中提到:

如上面例子所示,如果要求D對象爲單例,可以通過@Singleton註解來實現。首先我們需要在依賴圖中聲明對象是單例的:

@Module
public class DModule {
    @Provides
    @Singleton
    public D provideD() {
        return new D();
    }
}

DComponent接口也需要聲明:

@Singleton
@Component(modules = DModule.class)
public interface DComponent {
    D provideD();
}

如此,當我們注入D對象時,可保證每次注入的是同一個D對象:

DComponent dComponent = DaggerDComponent.create();
D d1 = dComponent.provideD(); 
D d2 = dComponent.provideD(); 
// d1 == d2

在我們看來,只是多加了一個註解而已,便實現了單例模式。要知道其原理,要從Dagger2生成的源碼入手。

Dagger2生成的源碼

以如下例子爲例:

  1. 定義類:

    public class A {
        public A(){
        }
    }
    
    public class B {
        A a;
    
        public B(A a) {
            this.a = a;
        }
    }
    
    public class C {
        A a;
        B b;
    
        public C(A a, B b) {
            this.a = a;
            this.b = b;
        }
    }
    
  2. 定義Module

    @Module
    public class ABCModule {
        @Provides
        public A provideA() {
            return new A();
        }
    
        @Provides
        public B provideB(A a) {
            return new B(a);
        }
    
        @Provides
        public C provideC(A a, B b) {
            return new C(a, b);
        }
    }
    
  3. 定義Component接口:

    @Component(module=ABCModule.class)
    public interface ABCComponent {
        public A provideA();
    
        public B provideB();
    
        public C provideC();
    
        void inject(Main main);
    }
    
  4. 依賴注入:

    public class Main {

    @Inject
    C c;
    
    public Main() {
        ABCComponent abcComponent = DaggerABCComponent
                .builder()
                .dComponent(dComponent)
                .build();
        A a = abcComponent.provideA();
        B b = abcComponent.provideB();
        abcComponent.inject(this);
    }

    }

編譯工程,Dagger2在項目路徑下生成了如下文件:

[dagger2]
┣━[di]
┃  ┣━[component]
┃  ┃  ┗━DaggerABCComponent.java
┃  ┗━[module]
┃      ┣━ABCModule_ProvideAFactory.java
┃      ┣━ABCModule_ProvideBFactory.java
┃      ┗━ABCModule_ProvideCFactory.java
┗━[model]
    ┗━Main_MembersInjector.java

(利用這個工具生成了文件結構圖)

注意,生成的文件在關聯的類相同路徑下。如DaggerABCComponent類生成在ABCComponent路徑下。

我們先來看看實際接觸到的DaggerABCComponent類:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerABCComponent implements ABCComponent {
  private Provider<A> provideAProvider;
  private Provider<B> provideBProvider;
  private Provider<C> provideCProvider;
  private MembersInjector<Main> mainMembersInjector;

  private DaggerABCComponent(Builder builder) {  
    assert builder != null;
    initialize(builder);
  }

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

  public static ABCComponent create() {  
    return builder().build();
  }

  private void initialize(final Builder builder) {  
    this.provideAProvider = ABCModule_ProvideAFactory.create(builder.aBCModule);
    this.provideBProvider = ABCModule_ProvideBFactory.create(builder.aBCModule, provideAProvider);
    this.provideCProvider = ABCModule_ProvideCFactory.create(builder.aBCModule, provideAProvider, provideBProvider);
    this.mainMembersInjector = Main_MembersInjector.create(provideCProvider);
  }

  @Override
  public A provideA() {  
    return provideAProvider.get();
  }

  @Override
  public B provideB() {  
    return provideBProvider.get();
  }

  @Override
  public C provideC() {  
    return provideCProvider.get();
  }

  @Override
  public void inject(Main main) {  
    mainMembersInjector.injectMembers(main);
  }

  public static final class Builder {
    private ABCModule aBCModule;
  
    private Builder() {  
    }
  
    public ABCComponent build() {  
      if (aBCModule == null) {
        this.aBCModule = new ABCModule();
      }
      return new DaggerABCComponent(this);
    }
  
    public Builder aBCModule(ABCModule aBCModule) {  
      if (aBCModule == null) {
        throw new NullPointerException("aBCModule");
      }
      this.aBCModule = aBCModule;
      return this;
    }
  }
}

來看幾個關鍵點:

  1. DaggerABCComponent繼承於ABCComponent。所以我們可以直接調用ABCComponent的方法。

  2. DaggerABCComponent需要Builder來進行初始化。Builder的作用是提供對象的module。

  3. 對象通過Provider從依賴圖中取出。Provider由Factory生成時會有類似依賴注入的操作。

  4. 通過MembersInjector進行依賴注入。

這幾個關鍵類的關係可用下圖表示:

+---------------------------------------+
|         DaggerABCComponent            |
|                                       |
|  +----------+  create     +-----------+------------+
|  |  Factory +-----+-----> |  Provider<A>           |
|  +----+-----+     |       +----+------+------------+
|       ^           |       |    |      |
|       |           |       | +--v------+------------+
|       |           +-----> | |Provider<B>           |
|       | ABCModule |       | +--+------+------------+
|       |           |       |    |      |
|       |           |       +----v------+------------+
|  +----+----+      +-----> |  Provider<C>           |
|  | Builder |              +-----------+------------+
|  +---------+                          |
|                                       |
| +--------------------+    +-----------+------------+
| |Main_MembersInjector+--> |  MembersInjector<Main> |
| +--------------------+    +-----------+------------+
|                                       |
+---------------------------------------+

其中最最關鍵的是Factory和Provider。以B類爲例,從依賴圖中取出B對象,需要經過如下代碼:

...
this.provideBProvider = ABCModule_ProvideBFactory.create(builder.aBCModule, provideAProvider);
...

@Override
public B provideB() {  
    return provideBProvider.get();
}

其中ABCModule_ProvideBFactory的源碼如下所示:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class ABCModule_ProvideBFactory implements Factory<B> {
  private final ABCModule module;
  private final Provider<A> aProvider;

  public ABCModule_ProvideBFactory(ABCModule module, Provider<A> aProvider) { // 根據之前的依賴關係,注入ProviderA  
    assert module != null;
    this.module = module;
    assert aProvider != null;
    this.aProvider = aProvider;
  }

  @Override
  public B get() {  
    B provided = module.provideB(aProvider.get()); // 從ProviderA中取出A對象,再生成B對象
    if (provided == null) {
      throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");
    }
    return provided;
  }

  public static Factory<B> create(ABCModule module, Provider<A> aProvider) {  
    return new ABCModule_ProvideBFactory(module, aProvider);
  }
}

Factory和Provider接口如下所示:

public interface Factory<T> extends Provider<T> {
}

public interface Provider<T> {
    T get();
}

從使用者的角度看,無需關心對象是如何生成的,只需調用provider的get方法即可獲得對象。而且對象應該是符合既定的規則並且初始化好可以馬上用的。

從ABCModule_ProvideBFactory(或者某個Provider)的角度看,在初始化方法裏就明確了自己所需依賴的對象(這裏是ProviderA)。在get方法的實現裏,只需關心B對象的生成。當需要A對象時,直接從外部“注入”的providerA取出即可。

再來看一看Main_MembersInjector的實現:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class Main_MembersInjector implements MembersInjector<Main> {
  private final Provider<C> cProvider;

  public Main_MembersInjector(Provider<C> cProvider) {  
    assert cProvider != null;
    this.cProvider = cProvider;
  }

  @Override
  public void injectMembers(Main instance) {  
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.c = cProvider.get();
  }

  public static MembersInjector<Main> create(Provider<C> cProvider) {  
      return new Main_MembersInjector(cProvider);
  }
}

Dagger2在編譯時會分析module中inject方法的參數的類型(這裏是Main類),記錄下用@Inject註解標註的成員,然後生成對應的Injector。

理解Injector的關鍵在理解它的構造方法和injectMembers方法。instance.c = cProvider.get();一句實施了依賴注入。

@Singleton

修改ABCModule如下所示:

    @Module
    public class ABCModule {
        ...

        @Provides
        @Singleton               // 添加Singleton註解
        public B provideB(A a) {
            return new B(a);
        }

        ...
    }

修改ABCComponent如下所示:

    @Singleton
    @Component(module=ABCModule.class)
    public interface ABCComponent {
        ...
    }

我們來看看Dagger2生成的代碼有什麼不同:

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerABCComponent implements ABCComponent {
...

private Provider provideBProvider;

...

private void initialize(final Builder builder) {

...
this.provideBProvider = ScopedProvider.create(ABCModule_ProvideBFactory.create(builder.aBCModule, provideAProvider));
...

}

...

@Override public B provideB() {

return provideBProvider.get();

}


...

}

可以看到唯一的不同是用ScopedProvider將ABCModule_ProvideBFactory包裹起來。來看一下ScopedProvider的源碼:

package dagger.internal;

import javax.inject.Provider;

public final class ScopedProvider<T> implements Provider<T> {
  private static final Object UNINITIALIZED = new Object();

  private final Factory<T> factory;
  private volatile Object instance = UNINITIALIZED;

  private ScopedProvider(Factory<T> factory) {
    assert factory != null;
    this.factory = factory;
  }

  @SuppressWarnings("unchecked") // cast only happens when result comes from the factory
  @Override
  public T get() {
    // double-check idiom from EJ2: Item 71
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          instance = result = factory.get();
        }
      }
    }
    return (T) result;
  }

  /** Returns a new scoped provider for the given factory. */
  public static <T> Provider<T> create(Factory<T> factory) {
    if (factory == null) {
      throw new NullPointerException();
    }
    return new ScopedProvider<T>(factory);
  }
}

理解上面的代碼關鍵在於:

  1. ScopedProvider在dagger.internal下,非Dagger2自動生成。

  2. ScopedProvider也是一個Provider

  3. 利用double-check,在instance上實現了單例模式。也就是說,在ScopedProvider的生命週期內,get返回的都是同一個對象。

以上3點實現了無入侵式的Singleton模式。但其實ScopedProvider並不是專爲Singleton模式設計的,Singleton模式只是Dagger2中Scope功能的效果。

@Scope

@Singleton註解的源碼如下所示:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

...

@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Scope {}

可以看出,@Singleton只是一個標記,表明這是一個Scope。那麼Scope是什麼呢?源代碼中有如此註釋:

A scope annotation applies to a class
containing an injectable constructor and governs how the injector reuses
instances of the type. By default, if no scope annotation is present, the
injector creates an instance (by injecting the type's constructor), uses
the instance for one injection, and then forgets it. If a scope annotation
is present, the injector may retain the instance for possible reuse in a
later injection.

簡單地說,@Scope決定了注射器從依賴圖中取出對象的行爲。如果節點有Scope標籤,那麼注入時將重用上次生成的對象。

依賴圖中某個節點標註了@Scope後,便擁有了與當前Component相同的生命週期。也就是說,如果要實現全局(Application範圍內)的Singleton,必需要有全局的Component。這就是爲什麼許多其他關於Dagger2的例子中,要在Application中保持ApplicationComponent的引用的原因。

至於在許多例子中看到的@PerActivity(對象在當前Activity的生命週期內唯一),@PerUser(對象在當前用戶態銷燬前唯一),它們的實現也是依賴於Component的生命週期。所以需要在Activity的onCreate中新建SomeActivityComponent並保持引用,在UserManager的login中新建UserComponent並保持引用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章