Dagger2注解大全

前言

??Dagger是幫助實現依賴注入的庫,雖然很多人都知道依賴注入對于架構設計的重要性,但是Dagger學習曲線十分陡峭,官方文檔更是看了幾遍也很難消化。本文旨在通過一篇文章來讓大家看懂并上手Dagger。
??Dagger最早由JakeWharton在square公司開發。后來轉由Google維護并發展為Dagger2。Dagger2區別于Dagger1的地方主要在于兩個,一個是由運行時通過反射構建依賴關系變為編譯期通過注解生成依賴關系,另一個是出錯時有更好地提醒(當然這也是因為Dagger2在編譯期間根據注解生成好了可讀性較好的代碼帶來的優勢)。 轉載請注明來源「Bug總柴」

參考

初學者建議先不要看官方文檔,可以先看這幾篇博客:

依賴注入

在學習Dagger之前,我們先來了解一下依賴注入。

什么是依賴注入

??依賴注入,顧名思義,就是說當代碼執行過程中需要某個服務對象的時候,不是通過當前代碼自己去構造或者去查找獲取服務對象,而是通過外部將這個服務對象傳給當前代碼。
??這樣做的好處在于當服務對象構建或者獲取方法改變時,不需要改變調用方的代碼,這也是S.O.L.I.D原則中開發封閉原則的具體表現。

如何實現依賴注入

在不使用Dagger等依賴注入庫的情況下,我們可以通過以下三種方式手動實現依賴注入。

  • 構造器依賴注入
// Constructor
Client(Service service) {
    // Save the reference to the passed-in service inside this client
    this.service = service;
}
  • Setter方法依賴注入
// Setter method
public void setService(Service service) {
    // Save the reference to the passed-in service inside this client.
    this.service = service;
}
  • 接口依賴注入
// Service setter interface.
public interface ServiceSetter {
    public void setService(Service service);
}

// Client class
public class Client implements ServiceSetter {
    // Internal reference to the service used by this client.
    private Service service;

    // Set the service that this client is to use.
    @Override
    public void setService(Service service) {
        this.service = service;
    }
}

Dagger2基本概念

Dagger2可以理解成就是在編譯階段根據注解構建一個依賴關系圖,然后根據依賴關系圖之間的依賴關系生成對象工廠類,在需要的地方注入對象。如何使用注解構造一個依賴關系圖是Dagger2使用的關鍵。在了解注解之前,我們先來認識一下以下三個概念:

bindings

bindings的概念是告訴Dagger注入器如何能得到一個具體類。有幾種方法可以表示當前代碼可以提供某個類型的對象:

  • 通過使用@Provides注解的非抽象方法返回一個類對象
@Provides
public Fruit providerApple() {
    return new Apple();
}
  • 通過@Binds注解的抽象方法,該抽象方法返回接口或抽象類,參數是一個該接口或者抽象類的具體實現類
@Binds
abstract Fruit bindApple(Apple apple);
  • 通過@Inject注解的構造方法
public class Apple implements Fruit {
    @Inject
    public Apple() {
    }
}
  • 通過multibindings@MapKey后面提到)或者producers(暫不細說)提供

modules

module是一個只有@Provides@Binds方法的類,用于集合所有的依賴關系。同時module可以通過inculdes來引入其他module從而得到其他module的依賴關系集合。例如:

@Module(includes = ProjectModule.class)
public abstract class FruitModule {
    @Binds 
    abstract Fruit bindApple(Apple apple);

    @Provides
    static Desk provideDesk() {
        return new Desk();
    }
}

components

component是被@Component標注的接口或者抽象類,Dagger會負責實例化一個componentcomponent中指定需要的modules,代表著這次依賴構建所有需要的全部依賴關系都可以從modules中找到。compoent中的方法只能是無參的,且這個無參方法的返回值就是Dagger最終需要構建得到的實體。可以說構建component中無參方法的返回值對象就是整個依賴關系查找的起源點。在構建這個實體時,如果遇到依賴,就會從modules中不斷地傳遞查找,直到所有的依賴都被找到為止。如果中間有某些依賴沒有注明實例化方式,Dagger會在編譯期間報錯。具體component的一個例子如下:

@Component(modules = {FruitModule.class, ProjectModule.class})
public interface FruitComponent {
    FruitShop inject();
}

bindings\modules\components的依賴關系圖可以表示為下圖所示

Dagger依賴圖.png

Dagger2注解

知道上面的概念可以看懂基本的Dagger代碼。不過Dagger有非常多幫助完成依賴關系圖構建的注解,只有把這些注解都弄懂了,才能真正看懂Dagger2的代碼。下面兩個圖可以看到一些常用的注解:

javax.inject.png
dagger.png

下面我們來一一介紹一下。

@Inject

@Injectjavax.inject包中的注解,可以用于對類的構造函數成員變量方法

用于類構造器中表示該類在依賴注入時使用被注解的構造器創建對象。

例如:

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

表示當其他地方依賴于FruitShop對象時,會使用FruitShop的默認構造方法進行創建。當被@Inject注解的構造函數是有參數的,那么Dagger會同時對其參數進行注入。例如:

public class FruitShop {
    @Inject
    public FruitShop(Desk desk) {
    }
}

當需要構建依賴關系時,在創建FruitShop的時候回對參數desk進行注入。
在生成的FruitShop_Factory.java代碼中,可以看到以下方法:

public final class FruitShop_Factory implements Factory<FruitShop> {
    private final Provider<Desk> deskProvider;
    
    public FruitShop_Factory(Provider<Desk> deskProvider) {
        this.deskProvider = deskProvider;
    }
    public static FruitShop provideInstance(Provider<Desk> deskProvider) {
        FruitShop instance = new FruitShop(deskProvider.get());
        return instance;
    }
}

@Inject用于構造函數需要注意兩點:

  1. 每個類只允許一個構造方法注解為@Inject,例如
public class FruitShop {
    // 由于有另外的構造函數注解了@Inject,這里不能再使用@Inject,否則編譯會出錯Error: Types may only contain one @Inject constructor.
    public FruitShop() {
    }
    
    @Inject
    public FruitShop(Location location) {
    }
}
  1. javax.inject.Inject文檔中說明當被注解的構造函數是public且無參的默認構造函數@Inject可以省略。但是實際Dagger2項目中,需要被注入的對象必須擁有@Inject注解的構造方法或者通過@Porvides注解的方法提供,否則會報錯Error: cannot be provided without an @Inject constructor or an @Provides-annotated method.。這一點Dagger的處理與javax.inject.Inject描述表現不一致。

用于成員變量中表示該成員變量作為依賴需要被注入

例如:

public class FruitShop {
    @Inject
    Fruit apple;
}

表示FruitShop中需要依賴水果apple,并希望由外部注入進來。
編譯之后我們會看到一個FruitShop_MembersInjector.java的類,里面會有一個這樣的方法:

public final class FruitShop_MembersInjector implements MembersInjector<FruitShop> {
    // Dagger生成代碼中會通過MembersInjector給我們對象需要的屬性進行注入
    public static void injectApple(FruitShop instance, Fruit apple) {
        instance.apple = apple;
    }
}

對于屬性注解需要注意被注解的屬性不能是final或者被private修飾符修飾。其中的原因在上面instance.apple = apple;代碼中不言而喻。
在生成的FruitShop_Factory.java代碼中,可以看到以下方法:

public final class FruitShop_Factory implements Factory<FruitShop> {
    private final Provider<Fruit> appleProvider;
    
    public FruitShop_Factory(Provider<Fruit> appleProvider) {
        this.appleProvider = appleProvider;
    }
    public static FruitShop provideInstance( Provider<Fruit> appleProvider) {
        FruitShop instance = new FruitShop();
        FruitShop_MembersInjector.injectApple(instance, appleProvider.get());
        return instance;
    }
}

用于方法中表示依賴于方法參數的類型會被注入

例如:

public class FruitShop {
    Desk mDesk;

    @Inject
    public void setDesk(Desk desk) {
        this.mDesk = desk;
    }
}

被注解的setDesk()方法有一個Desk類型的參數,意味著需要對Desk進行依賴注入。Dagger生成的代碼如下所示:

public final class FruitShop_Factory implements Factory<FruitShop> {
    private final Provider<Desk> deskProvider;
    
    public FruitShop_Factory(Provider<Desk> deskProvider) {
        this.deskProvider = deskProvider;
    }
    public static FruitShop provideInstance(Provider<Desk> deskProvider) {
        FruitShop instance = new FruitShop();
        FruitShop_MembersInjector.injectSetDesk(instance, deskProvider.get());
        return instance;
    }
}
public final class FruitShop_MembersInjector implements MembersInjector<FruitShop> {
    public static void injectSetDesk(FruitShop instance, Desk desk) {
        instance.setDesk(desk);
    }
}

@Inject用于注解方法需要注意被注解的方法不能是private的。被注解的方法支持擁有多個參數。如果標注在public方法上,Dagger2會在構造方法執行之后立即調用這個方法。

@Provides & @Module & @Component

使用@Inject來標記依賴的注入不是什么時候都可以的,例如第三方api的代碼我們是不能修改的,沒辦法通過@Inject注解第三方api類的構造器,從而沒辦法對第三方api的對象進行構建和依賴注入。這個時候我們可以使用@Provides來提供對應的依賴。而@Provides必須放到一個被@Module注解的類中。例如:

// 通過在module中使用@Provides表示提供依賴的方法
@Module
public  class FruitModule {
    @Provides
    Fruit provideApple() {
        return new Apple();
    }
}

// 使用@Inject說明需要依賴注入的地方
public class FruitShop {
    // 這里需要提供一個Fruit類型的依賴
    @Inject
    Fruit apple;

    @Inject
    public FruitShop() {
    }
}

// 將需要用到依賴的地方FruitShop和提供依賴的地方FruitModule綁定在一起
@Component(modules = FruitModule.class)
public interface FruitComponent {
    FruitShop inject();
}

這里在module中聲明了一個可以提供Apple類依賴的方法provideApple()。并且component將依賴的需求方和提供方都綁定在了一起。我們來看生成的代碼

public final class FruitModule_ProvideAppleFactory implements Factory<Fruit> {
  private final FruitModule module;

  public FruitModule_ProvideAppleFactory(FruitModule module) {
    this.module = module;
  }

  @Override
  public Fruit get() {
    return provideInstance(module);
  }

  public static Fruit provideInstance(FruitModule module) {
    return proxyProvideApple(module);
  }

  public static FruitModule_ProvideAppleFactory create(FruitModule module) {
    return new FruitModule_ProvideAppleFactory(module);
  }

  public static Fruit proxyProvideApple(FruitModule instance) {
    return Preconditions.checkNotNull(
        instance.provideApple(), "Cannot return null from a non-@Nullable @Provides method");
  }
}

這段生成的代碼實際上是提供Apple類工廠FruitModule_ProvideAppleFactory,能夠通過provideApple()提供Apple對象。以下的代碼中,component通過傳遞FruitModule_ProvideAppleFactory對象到FruitShop_Factory中完成對FruitShop的依賴注入

public final class DaggerFruitComponent implements FruitComponent {
  private FruitModule_ProvideAppleFactory provideAppleProvider;
  
  private void initialize(final Builder builder) {
    this.provideAppleProvider = FruitModule_ProvideAppleFactory.create(builder.fruitModule);
    this.fruitShopProvider = DoubleCheck.provider(FruitShop_Factory.create(provideAppleProvider));
  }
}

通過@Provides @Module @Component 三個注解就可以完成最基本的依賴注入關系圖的構造,從而使用Dagger給依賴進行注入。這里需要注意:

  • 通過@Provides注解的方法不能返回null,否則會報NullPointerException。如果@Provides方法可能返回null,那需要加上注入@Nullable,同時在需要依賴注入的地方加上@Nullable標注。
  • 一般module類都使用XXXModule命名,而provide方法一般都使用provideXXX命名方式。

@Binds

@Binds的作用和@Provides的作用是一樣的,是提供接口依賴的一種簡潔表示的方式。例如下面這個例子:

@Module
public  class FruitModule {
    @Provides
    Fruit provideApple() {
        return new Apple();
    }
}

使用@Binds可以簡化為:

@Module
abstract public  class FruitModule {
    @Binds
    abstract Fruit bindApple(Apple apple);
}

表示當需要依賴Furit接口時,使用Apple實例對象進行注入。需要注意的是,使用@Binds標注的方法必須有且僅有一個方法參數,且這個方法參數是方法返回值的實現類或者子類。

@Component

因為Componet較為復雜,拿出來再單獨說一下。Component的聲明如下:

public @interface Component {
    Class<?>[] modules() default {};
    Class<?>[] dependencies() default {};
    @interface Builder {}
}

這代表著@Component的標簽中除了可以指定modules之外還可以通過dependencies引用其他的component。在被@Component注解的類必須是接口或者抽象類,這個被注解的類中可以包含以下三個東西:

  1. 表示需要提供的依賴的方法,例如:
// 表示需要注入依賴生成SomeType類對象
SomeType getSomeType();
// 表示需要注入依賴生成Set<SomeType>對象,multibinding后面會介紹
Set<SomeType> getSomeTypes();
// 表示需要注入生成一個Qualifier為PortNumber的int整形,Qualifier后面會介紹
@PortNumber int getPortNumber();
// 表示需要注入依賴生成Provider<SomeType>對象,Provider<>后面介紹
Provider<SomeType> getSomeTypeProvider();
// 表示需要注入依賴生成Lazy<SomeType>對象,Lazy<>后面會介紹
Lazy<SomeType> getLazySomeType();
  1. 表示需要注入成員依賴的方法,
// 表示需要將someType中標記為依賴的屬性和方法進行注入
void injectSomeType(SomeType someType);
// 表示需要將someType中標記為依賴的屬性和方法進行注入,并返回SomeType對象
SomeType injectAndReturnSomeType(SomeType someType);
  1. 構造Component的Builder
    Dagger生成Component實現類時,會自動根據Bulder模式生成所需要Builder類。當Component所依賴的Module為非抽象且默認構造函數為private時,則Dagger會生成對應的有傳入module方法的Builder類,例如:
@Component(modules = ProjectModule.class)
public interface FruitComponent {
    FruitShop inject();
}

@Module
public class ProjectModule {
    private Desk mDesk;

    private ProjectModule(){}
    
    public ProjectModule(Desk desk){
        mDesk = desk;
    }
    
    @Provides
    public Desk provide() {
        return mDesk;
    }
}

則在生成的DaggerFruitComponent中會有以下Builder方法

public final class DaggerFruitComponent implements FruitComponent {
  private ProjectModule projectModule;
  
  public static final class Builder {
    private ProjectModule projectModule;

    private Builder() {}

    public FruitComponent build() {
      if (projectModule == null) {
        throw new IllegalStateException(ProjectModule.class.getCanonicalName() + " must be set");
      }
      return new DaggerFruitComponent(this);
    }

    public Builder projectModule(ProjectModule projectModule) {
      this.projectModule = Preconditions.checkNotNull(projectModule);
      return this;
    }
  }
}

在調用時需要傳入依賴的module:

FruitShop fruitShop = DaggerFruitComponent
                    .builder()
                    .projectModule(new ProjectModule(new Desk()))
                    .build()
                    .inject();

當Component所依賴的module和其他Componet都不需要使用有參的構造函數的話,Component可以使用簡潔的create()方法,例如將上面的module改為:

@Module
public class ProjectModule {
    
    @Provides
    public Desk provide() {
        return new Desk();
    }
}

則生成的componet會是這樣的:

public final class DaggerFruitComponent implements FruitComponent {
  public static FruitComponent create() {
    return new Builder().build();
  }
  public static final class Builder {
    private ProjectModule projectModule;

    private Builder() {}

    public FruitComponent build() {
      if (projectModule == null) {
        this.projectModule = new ProjectModule();
      }
      return new DaggerFruitComponent(this);
    }

    public Builder projectModule(ProjectModule projectModule) {
      this.projectModule = Preconditions.checkNotNull(projectModule);
      return this;
    }
  }
}

在調用時僅需調用create()方法既可

FruitShop fruitShop = DaggerFruitComponent.create().inject();

@Qualifier

在上面了解完@Inject之后,大家可能有個疑惑,使用@Inject注入的對象如果是接口或者抽象類怎么辦呢?在不同的地方可能需要不同的接口或者抽象類的實現,怎么讓Dagger知道我究竟需要的哪種實現類呢?例如:

public class FruitShop {
    @Inject
    Fruit apple;

    @Inject
    Fruit orange;
}

??這里代碼需要對appleorange進行注入,但是對于Fruit的注入只能聲明一個,所以這個地方appleorange要么都被注入成class Apple implements Fruit或者class Orange implements Fruit
??@Qualifier這個時候就能作為一個限定符派上用場了。@Qualifier是加在注解之上的注解(也稱為元注解),當需要注入的是接口或者抽象類,就可以使用@Qualifier來定義一個新的注解用來表明對應需要的依賴關系。使用@Qualifier可以實現指定apple需要用Apple注入,orange需要使用Orange類注入。例如我們可以這樣實現

// 首先定義一個表示水果類型的注解
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface FruitType {
    String value() default "";
}
// 接著使用這個注解表示對應依賴關系
@Module
public abstract class FruitModule {
    @Binds @FruitType("apple")
    abstract Fruit bindApple(Apple apple);

    @Binds @FruitType("orange")
    abstract Fruit bindOrange(Orange orange);
}
// 使用時標記相應的注解既可
public class FruitShop {
    @Inject @FruitType("apple")
    Fruit apple;

    @Inject @FruitType("orange")
    Fruit orange;
}

除了可以聲明value為String的注解外,還可以傳入其他類型,例如我們需要一張顏色是紅色的桌子:

@java.lang.annotation.Documented
@java.lang.annotation.Retention(RUNTIME)
@javax.inject.Qualifier
public @interface DeskColor {
    Color color() default Color.RED;
    public enum Color { RED, WHITE }
}

在Module中可以指定具體生成的對象:

@Module
public class ProjectModule {

    @Provides @DeskColor(color = DeskColor.Color.RED)
    public Desk provideDesk() {
        return new Desk("RED");
    }
}

在使用時再進行標記既可:

public class FruitShop {

    @Inject
    public FruitShop() {
    }

    @Inject @DeskColor(color = DeskColor.Color.RED)
    Desk desk;
}

通過@Qualifier定義注解可以實現對同一個接口或抽象類的指定不同對象注入。

@Named

了解完@Qualifier之后再看看@Name的聲明:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}

可以看出除了接口名稱不一樣之外,其余的和上面定義的@FruitType是一致的,所以其實@Named只是系統定義好的,參數為String的默認限定符。將上面代碼中的@FruitType改成@Named能達到一樣的效果。

@Scope和@Singleton

@Scope是另一個元注解,它的作用是告訴注入器要注意對象的重用的生命周期。其中@Scope的聲明如下:

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

我們再看@Singleton的聲明:

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

可以發現@Singleton就是被@Scope聲明的注解,可以作為一個生命周期的注解符。
例如我們需要注入一個Desk類,我們希望一個FruitShop對應只有一個Desk,正常的情況下我們是這樣聲明的:

public class FruitShop {

    @Inject
    public FruitShop() {
    }

    @Injec
    Desk desk;

    @Inject
    Desk desk2;
    
    public String checkDesk() {
        return desk == desk2 ? "desk equal" : "desk not equal";
    }
}

@Module
public class ProjectModule {

    @Provides
    public Desk provideDesk() {
        return new Desk();
    }
}

@Component(modules = ProjectModule.class)
public interface FruitComponent {
    FruitShop inject();
}

在Main函數中執行

public class Main {

    public static void main(String[] args) {
        FruitShop fruitShop = DaggerFruitComponent.create().inject();
        System.out.println(fruitShop.checkDesk());
    }
}

得到的結果是

desk not equal

Process finished with exit code 0

當然這不是我們希望得到的結果,下面我們來用@Singleton改造一下如下:

public class FruitShop {

    @Inject
    public FruitShop() {
    }

    @Injec
    Desk desk;

    @Inject
    Desk desk2;
    
    public String checkDesk() {
        return desk == desk2 ? "desk equal" : "desk not equal";
    }
}

@Module
public class ProjectModule {

    @Provides
    @Singleton
    public Desk provideDesk() {
        return new Desk();
    }
}

@Singleton
@Component(modules = ProjectModule.class)
public interface FruitComponent {
    FruitShop inject();
}

現在Rebuild之后再運行一下:

desk equal

Process finished with exit code 0

和之前不同的地方在于我們隊Component和module中的provide方法都加了@Singleton標記。我們來看看對比下前后生成的代碼有什么區別:

diff.png

可以看出,兩次生成的代碼中,只有DaggerFruitComponent有區別,其中的區別在于在Component中provideDeskProvider在有@Singleton標注的例子中是單例的存在:

  private void initialize(final Builder builder) {
    // DoubleCheck.provider就是用了雙重檢驗的單例模式提供單例
    this.provideDeskProvider =
        DoubleCheck.provider(ProjectModule_ProvideDeskFactory.create(builder.projectModule));
  }
  
  private FruitShop injectFruitShop(FruitShop instance) {
    FruitShop_MembersInjector.injectDesk(instance, provideDeskProvider.get());
    FruitShop_MembersInjector.injectDesk2(instance, provideDeskProvider.get());
    return instance;
  }

其中DoubleCheck.get()方法使用雙重判斷獲取單例。

public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
  @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;
  }
}

而在沒有使用@Singletion的例子中,并沒有使用單例來提供Desk對象:

  private void initialize(final Builder builder) {
    this.projectModule = builder.projectModule;
  }
  private FruitShop injectFruitShop(FruitShop instance) {
    FruitShop_MembersInjector.injectDesk(
        instance, ProjectModule_ProvideDeskFactory.proxyProvideDesk(projectModule));
    FruitShop_MembersInjector.injectDesk2(
        instance, ProjectModule_ProvideDeskFactory.proxyProvideDesk(projectModule));
    return instance;
  }

通過上面的例子可以看出@Scope是用來定義需要的依賴對象在一個Component依賴關系圖生成中是否需要重用,且重用的范圍在一個Component對象的引用范圍內。至于@Scope的意義在于可以在Components之間的依賴中使得依賴對象在不同的Components中重用。Components之間的依賴會在后面介紹。

這里需要注意幾點:

  1. @Scope注解的注解不能用于標注依賴的構造函數
  2. 沒有被@Scope注解的注解(如@Singleton)注解的componet不能存在被@Scope注解的注解(如@Singleton)注解的方法(有點繞,可以理解成沒有標志為@Singleton的componet不能擁有標志為@Singleton的方法)
  3. 如果componet定義了一個scope,那么這個componet里面只能存在沒有scoped的依賴關系,或者擁有跟componet一樣scope的依賴關系
  4. 使用Componet的調用方需要負責重用范圍的定義,例如希望有一個全局的單例,那么則需要保存一個擁有全局生命周期的component依賴生成類對象。

@Reusable

@Singleton類似的,@Reusable也是被@Scope注釋的注釋。與@Singleton不同的是,@Reusable只表示Dagger生成的對象可以被緩存起來,從而節省內存消耗,但是不能保證對象的單例性質。我們將上面例子中的@Singleton改成@Reusable

@Module
public class ProjectModule {

    @Provides
    @Reusable
    public Desk provideDesk() {
        return new Desk();
    }
}

rebuild之后我們來看生成的代碼

public final class DaggerFruitComponent implements FruitComponent {
  private void initialize(final Builder builder) {
    this.provideDeskProvider =
        SingleCheck.provider(ProjectModule_ProvideDeskFactory.create(builder.projectModule));
  }
  private FruitShop injectFruitShop(FruitShop instance) {
    FruitShop_MembersInjector.injectDesk(instance, provideDeskProvider.get());
    FruitShop_MembersInjector.injectDesk2(instance, provideDeskProvider.get());
    return instance;
  }
}

其中SingleCheck.get()方法如下:

public final class SingleCheck<T> implements Provider<T> {
  @Override
  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<T> 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;
  }
}

與剛剛的區別是由DoubleCheck.provider變成了SingleCheck.provider,從代碼實現可以看出@Reusable并不是嚴格的單例模式,只是對對象進行了緩存。

@Component的dependencies和@SubComponent

雖然獨立的沒有scope范圍的component已經非常實用了,但是在某些情況可能需要用到多個不同scope的不同componet。不同的Component之間可以通過指定依賴關系來聯系起來。Components之間的關聯可以采取兩種方式:指定指定dependencies或者SubComponet。下面我們來看看二者的區別。

-指定dependencies

當一個Component需要從另一個Componet中獲得依賴的時候,可以使用@Component(dependencies = {XXXComponent.class})來引用其他component的依賴。需要注意的是,被引用的Component需要顯示暴露出給外部的依賴,不然編譯會報錯。看下面這個例子。

@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
    FruitShop inject();

    // 對外暴露的依賴,表示其他Component可以從這個Component中
    // 獲得@FruitType為apple的類型為Fruit的依賴
    @FruitType("apple")
    Fruit getApple();
}

在有了水果的依賴之后,我們創建一個果汁的依賴關系:

public interface Juice {
    String name();
}

public class AppleJuice implements Juice {

    private Fruit mApple;

    // 這里要構建一個蘋果汁,需要用到蘋果,這個依賴需要從FruitComponent中獲得
    @Inject
    public AppleJuice(@FruitType("apple") Fruit apple) {
        mApple = apple;
    }

    @Override
    public String name() {
        return mApple.name();
    }
}

@Module
abstract public class JuiceModule {

    @OtherScop
    @Binds @JuiceType("appleJuice")
    abstract Juice bindAppleJuice(AppleJuice appleJuice);

}

// 這里通過指定dependencies,指出JuiceComponent需要FruitComponent作為依賴
@OtherScop
@Component(dependencies = {FruitComponent.class}, modules = {JuiceModule.class})
public interface JuiceComponent {
    JuiceShop inject();
}

public class JuiceShop {

    @Inject
    public JuiceShop(){}

    // 構建果汁商店需要一個蘋果汁,Dagger將負責構建
    @Inject
    @JuiceType("appleJuice")
    public Juice appleJuice;

    public String getJuice() {
        return appleJuice.name();
    }
}

上面的代碼,關注JuiceComponent類,這個類本身依賴關系圖從JuiceModule中獲得,而JuiceModule類只是聲明了JuiceTypeappleJuiceJuice類通過創建AppleJuice獲得。而觀察AppleJuice類需要一個FruitTypeappleFruit類作為依賴。這個Fruit類的依賴并不能從JuiceComponent中獲得,因此我們指定擁有這個Fruit類依賴的dependencies = {FruitComponent.class}。在FruitComponent類中需要顯示聲明其可以提供FruitTypeappleFruit類如下:

@FruitType("apple")
Fruit getApple();

因此通過指定dependencies = {FruitComponent.class}構成了完整的依賴關系鏈,我們可以如下構建一個JuiceShop:

public static void main(String[] args) {
    JuiceShop juiceShop = DaggerJuiceComponent
            .builder()
            .fruitComponent(DaggerFruitComponent.create())
            .build()
            .inject();
    System.out.println(juiceShop.getJuice());
}

Dagger會給我們生成DaggerJuiceComponent,并通過fruitComponent()方法,放入DaggerFruitComponent的依賴。我們來看看Dagger生成的DaggerJuiceComponent具體是如何使用DaggerFruitComponent來生成依賴的:

public final class DaggerJuiceComponent implements JuiceComponent {
    private com_shen_example_di_FruitComponent_getApple getAppleProvider;
    
    private DaggerJuiceComponent(Builder builder) {
        initialize(builder);
    }
    
    private void initialize(final Builder builder) {
        // 保存fruitComponent到com_shen_example_di_FruitComponent_getApple內部類中
        this.getAppleProvider = new com_shen_example_di_FruitComponent_getApple(builder.fruitComponent);
        // 將保存有fruitComponent的內部類傳遞給AppleJuice構造工廠
        this.appleJuiceProvider = AppleJuice_Factory.create(getAppleProvider);
    }
    
    private static class com_shen_example_di_FruitComponent_getApple implements Provider<Fruit> {
        private final FruitComponent fruitComponent;

        com_shen_example_di_FruitComponent_getApple(FruitComponent fruitComponent) {
        this.fruitComponent = fruitComponent;
        }

        // 通過fruitComponent創建apple
        @Override
        public Fruit get() {
        return Preconditions.checkNotNull(
            fruitComponent.getApple(), "Cannot return null from a non-@Nullable component method");
        }
    }
    
    public static final class Builder {
        private FruitComponent fruitComponent;

        public JuiceComponent build() {
            return new DaggerJuiceComponent(this);
        }

        public Builder fruitComponent(FruitComponent fruitComponent) {
            this.fruitComponent = Preconditions.checkNotNull(fruitComponent);
            return this;
        }
  }

}

public final class AppleJuice_Factory implements Factory<AppleJuice> {
  private final Provider<Fruit> appleProvider;

  public AppleJuice_Factory(Provider<Fruit> appleProvider) {
    this.appleProvider = appleProvider;
  }

  @Override
  public AppleJuice get() {
    return provideInstance(appleProvider);
  }

  public static AppleJuice provideInstance(Provider<Fruit> appleProvider) {
    // 最終通過調用保存有fruitComponent的get方法,
    // 通過fruitComponent創建apple,并傳入給AppleJuice構造函數中
    return new AppleJuice(appleProvider.get());
  }

  public static AppleJuice_Factory create(Provider<Fruit> appleProvider) {
    return new AppleJuice_Factory(appleProvider);
  }

}

通過上述Dagger生成的代碼可以看出,通過dependencies方式指定Component依賴,Dagger會將依賴的Component通過組合方式傳入給目標的Component,并在目標Component需要創建依賴時,通過組合傳入的依賴Component進行依賴類的構建。再次強調,如果沒有在依賴Component中聲明其對外暴露的依賴,會出現報錯。例如假設我們將上面的FruitComponent去掉getApple方法:

@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
    FruitShop inject();
}

那么在編譯時會出現報錯:

Error:(8, 8) java: [Dagger/MissingBinding] @com.shen.example.di.FruitType("apple") com.shen.example.fruit.Fruit cannot be provided without an @Provides-annotated method.
      @com.shen.example.di.FruitType("apple") com.shen.example.fruit.Fruit is injected at
          com.shen.example.juice.AppleJuice(apple)
      com.shen.example.juice.AppleJuice is injected at
          com.shen.example.di.JuiceModule.bindAppleJuice(appleJuice)
      @com.shen.example.di.JuiceType("appleJuice") com.shen.example.juice.Juice is injected at
          com.shen.example.JuiceShop.appleJuice
      com.shen.example.JuiceShop is provided at
          com.shen.example.di.JuiceComponent.inject()

-@SubComponent

@SubComponent聲明的接口或者抽象類,表示其本身的依賴關系圖是不完整的,必須通過依附于外部的Component才能獲得完整的依賴關系。使用@SubComponent有兩種方式,第一種是通過在被依賴的Component中聲明返回SubComponent類型的方法,并使用SubComponent中聲明的需要傳入參數的Module作為參數。第二種是在Component聲明的Module中,通過Module.subcomponents指定這個Module可以為哪些SubComponent提供依賴來源。
我們先看第一種方法,對比使用dependencies方式,只需要改變以下兩個類:

@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
    FruitShop inject();
    // 通過在被依賴的Component中聲明返回SubComponent類型的方法,
    // 并使用SubComponent中聲明的需要傳入參數的Module作為參數。
    // 由于JuiceComponent沒有指定有參的Module,因此這里方法的參數可以為空
    JuiceComponent juiceComponent();
}

// 將JuiceComponent標注為Subcomponent,去掉dependencies指定
@OtherScop
@Subcomponent(modules = {JuiceModule.class})
public interface JuiceComponent {
    JuiceShop inject();
}

我們可以如下構建一個JuiceShop:

public static void main(String[] args) {
    JuiceShop juiceShop = DaggerFruitComponent
                .builder()
                .build().juiceComponent()
                .inject();
    System.out.println(juiceShop.getJuice());
}

我們來看生成的DaggerFruitComponent

public final class DaggerFruitComponent implements FruitComponent {
    // 通過FruitComponent中轉至JuiceComponent
    @Override
    public JuiceComponent juiceComponent() {
        return new JuiceComponentImpl();
    }
    
    private final class JuiceComponentImpl implements JuiceComponent {
        private AppleJuice_Factory appleJuiceProvider;
        private Provider<Juice> bindAppleJuiceProvider;

        private JuiceComponentImpl() {
            initialize();
        }

        @SuppressWarnings("unchecked")
        private void initialize() {
            this.appleJuiceProvider = AppleJuice_Factory.create((Provider) Apple_Factory.create());
            this.bindAppleJuiceProvider = DoubleCheck.provider((Provider) appleJuiceProvider);
        }

        // 最終會通過中轉得到的JuiceComponent,調用inject方法得到目標對象
        @Override
        public JuiceShop inject() {
            return injectJuiceShop(JuiceShop_Factory.newJuiceShop());
        }

        @CanIgnoreReturnValue
        private JuiceShop injectJuiceShop(JuiceShop instance) {
            JuiceShop_MembersInjector.injectAppleJuice(instance, bindAppleJuiceProvider.get());
            return instance;
        }
    }
}

可以看出,當使用@SubModule時,JuiceComponent被聲明為FruitComponent的內部類,通過內部中轉至JuiceComponent從而構造出目標對象。
第二種使用@Module.subcomponents,相比第一種SubComponent方法而言,不需要在在被依賴的Component中聲明返回SubComponent類型的方法,只需要在被依賴的Component對應的Module中聲明subcomponent既可。同時對SubComponent要求有@Subcomponent.Builder。
我們看FruitComponent不在需要聲明返回JuiceComponent的方法

@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
    FruitShop inject();
    // 不需要額外聲明SubComponent
    //JuiceComponent juiceComponent();
}

@OtherScop
@Subcomponent(modules = {JuiceModule.class})
public interface JuiceComponent {
    JuiceShop inject();

    // 需要添加SubComponent.Builder
    @Subcomponent.Builder
    interface Builder {
        JuiceComponent build();
    }
}

同時對于JuiceComponent需要依賴的module添加subComponent依賴

// 對Module加入subcomponents = {JuiceComponent.class}
@Module(subcomponents = {JuiceComponent.class})
abstract public  class FruitModule {

    @Binds @FruitType("apple")
    abstract Fruit bindApple(Apple apple);

    @Binds @FruitType("orange")
    abstract Fruit bindOrange(Orange orange);

}

這個時候可以在被依賴的Component生成產物的FruitShop中構造出JuiceShop

public class FruitShop {
    // 這里可以直接使用JuiceComponent.Builder,Provider的作用后面再說
    @Inject
    public Provider<JuiceComponent.Builder> juiceComponentProvider;

    @Inject
    public FruitShop() {}
    
    public String juice() {
        // 通過聲明需要注入一個JuiceComponent,從而獲得JuiceShop
        JuiceShop juiceShop = juiceComponentProvider.get().build().inject();
        return juiceShop.getJuice();
    }

}

從生成的DaggerFruitComponent來看

public final class DaggerFruitComponent implements FruitComponent {
  private Provider<JuiceComponent.Builder> juiceComponentBuilderProvider;
  
  // 初始化juiceComponentBuilderProvider
  private void initialize(final Builder builder) {
    this.juiceComponentBuilderProvider =
        new Provider<JuiceComponent.Builder>() {
          @Override
          public JuiceComponent.Builder get() {
            return new JuiceComponentBuilder();
          }
        };
  }
  
  // 將juiceComponentBuilderProvider注入到FruitShop中
  private FruitShop injectFruitShop(FruitShop instance) {
    FruitShop_MembersInjector.injectJuiceComponentProvider(instance, juiceComponentBuilderProvider);
    return instance;
  }
  
  private final class JuiceComponentBuilder implements JuiceComponent.Builder {
    // 通過JuiceComponent.Builder生成JuiceComponentImpl,這就是為什么通過@Module.subcomponents一定要聲明Builder的原因。
    @Override
    public JuiceComponent build() {
      return new JuiceComponentImpl(this);
    }
  }
  
  // JuiceComponentImpl與第一種的SubComponent方法內容類似,省略
  private final class JuiceComponentImpl implements JuiceComponent {
    // ……
  }
}

-指定dependencies與SubComponent區別

  • dependencies可以同時指定多個,而采用SubComponent只能有一個parent Component
  • dependencies指定的Component與本身的Component是屬于組合關系,他們各自獨立,可以單獨使用。而SubComponent必須依賴于某個Component,Dagger不會對SubComponent生成DaggerXXXSubComponent類,而是在DaggerXXXComponent中定義了SubComponentImpl的內部類。
  • 調用生成對象的時候依賴方向不同。使用dependencies方式,需要外部依賴的Componet和被依賴的Componet之間相互獨立,會生成兩個DaggerXXXComponet,并且是通過需要依賴的Componet發起,通過引入外部的Component來構建出最終的對象;而通過@SubComponent方式則是只生成一個DaggerXXXComponent,由被依賴的Component發起,通過中轉至需要其依賴的內部Component或者從依賴的Component生成對象內部來構建出最終對象。見如下代碼:
// dependencies方式
JuiceShop juiceShop = DaggerJuiceComponent
                .builder()
                .fruitComponent(DaggerFruitComponent.create())
                .build()
                .inject();

// @SubComponent第一種方式
JuiceShop juiceShop = DaggerFruitComponent
        .builder()
        .build().juiceComponent()
        .inject();
        
// @SubComponent第二種方式
public class FruitShop {
    @Inject
    public Provider<JuiceComponent.Builder> juiceComponentProvider;

    @Inject
    public FruitShop() {}
    
    public void createJuiceShop() {
        JuiceShop juiceShop = juiceComponentProvider.get().build().inject();
    }
}

??使用SubComponent的有兩個好處,第一個是可以對不同的component聲明不同的生命周期,規范對象存活的周期。第二個是為了更好的封裝,將相同的依賴放置到同一個component并依賴于它,而將不同的依賴封裝到不同的模塊。
??對于Component的依賴關系介紹到這里。在平常的使用中,如果module之間依賴較多的話,不建議采用@SubComponent第一種方式,因為這種方式每增加一個submodule都要在被依賴的component中聲明。如果被依賴的component比較穩定,建議使用dependencies方式,這樣新增加一個依賴的component不用修改被依賴的component。而@SubComponent第二種方式僅適用于依賴的component是作為被依賴component的一個附屬情況下使用,因為subcomponent無法脫離被依賴component的構建產物使用。不過第二種@SubComponent方式相對第一種方式而言,會讓Dagger知道SubComponent是否被使用,從而減少生成沒有被使用的SubComponent的代碼。

Lazy<> & Provider<>

依賴注入有三種模式,一種是最常見的直接注入(Direct Injection),還有就是懶注入(Lazy Injection)和提供者注入(Provider Injection)。直接注入模式下,被注入的對象會先生成,然后當有需要被注入的地方時,將預先生成的對象賦值到需要的地方。Lazy注入只有當get的時候才會創建對象,且生成之后對象會被緩存下來。Provider注入在每次get都會創建新的對象。
用官方的一個例子來說明。

@Module
public class CounterModule {
    private int next = 100;

    @Provides
    Integer provideInteger() {
        System.out.println("computing...");
        return next++;
    }
}

CounterModule可以提供一個整形變量,每次提供完之后會對這個變量加一。

/**
 * 直接注入
 */
public class DirectCounter {
    @Inject
    Integer value;

    void print() {
        System.out.println("direct counter printing...");
        System.out.println(value);
        System.out.println(value);
        System.out.println(value);
    }
}

/**
 * Provider注入
 */
public class ProviderCounter {
    @Inject
    Provider<Integer> provider;

    void print() {
        System.out.println("provider counter printing...");
        System.out.println(provider.get());
        System.out.println(provider.get());
        System.out.println(provider.get());
    }
}

/**
 * Lazy注入
 */
public class LazyCounter {
    @Inject
    Lazy<Integer> lazy;

    void print() {
        System.out.println("lazy counter printing...");
        System.out.println(lazy.get());
        System.out.println(lazy.get());
        System.out.println(lazy.get());
    }
}

/**
 * 多個Lazy注入,lazy與單例
 */
public class LazyCounters {
    @Inject
    LazyCounter counter1;
    @Inject
    LazyCounter counter2;

    void print() {
        System.out.println("lazy counters printing...");
        counter1.print();
        counter2.print();
    }
}

我們將這幾種的Counter集合到一起并輸入

public class Counter {
    @Inject
    DirectCounter mDirectCounter;

    @Inject
    ProviderCounter mProviderCounter;

    @Inject
    LazyCounter mLazyCounter;

    @Inject
    LazyCounters mLazyCounters;

    public void print() {
        mDirectCounter.print();
        mProviderCounter.print();
        mLazyCounter.print();
        mLazyCounters.print();
    }
}

得到以下的輸入結果:

// 直接注入
computing...
direct counter printing...
100
100
100
// Provider注入
provider counter printing...
computing...
101
computing...
102
computing...
103
// Lazy注入
lazy counter printing...
computing...
104
104
104
// 多個Lazy注入
lazy counters printing...
lazy counter printing...
computing...
105
105
105
lazy counter printing...
computing...
106
106
106

從結果可以看出,直接注入會先計算一次得到需要被注入的依賴對象(這里是整型100),并在需要的地方都返回這個預先計算好的對象,因此都返回100。
Provider注入則會在每次get方法調用的地方都通過Module中的provider方法計算得到需要被注入的依賴對象,因此依次返回新計算的對象101、102、103。
Lazy注入與直接注入相似,只會計算一次需要被注入的依賴對象,但是與直接注入不同的是,Lazy注入只有在被調用get方法的時候才會進行計算,因此可以看到lazy counter printing...先打印,然后才是computing...
需要注意的是Lazy注入并不等同于單例模式,不同的LazyCounter的get方法會獲取到不同的對象。例如LazyCounters中通過兩個LazyCounter的get方法分別獲取到的是105和106,并且lazy counter printing...computing...都打印了兩次。

@BindsInstance

當構建Component的時候,如果需要外部傳入參數,我們有兩種方法,一種是通過構建Module時通過Module的構造函數傳入參數,第二種是通過@BindsInstance方式,在構建Component的時候通過Component.Builder來構建Component。我們先看第一種方法:

// Pear對象需要一個String類型的名稱
public class Pear implements Fruit {

    String customName;

    public Pear(String name) {
        customName = name;
    }

    @Override
    public String name() {
        if (customName != null && customName.length() > 0) {
            return customName;
        } else {
            return "pear";
        }
    }
}

// ProjectModule的構造函數接收一個String類型的參數,并最終用于構造Pear對象
@Module
public class ProjectModule {

    String name;

    public ProjectModule(String name) {
        this.name = name;
    }

    @Provides @Name
    public String provideName() {
        return name;
    }

    @Provides @FruitType("pear")
    public Fruit providerPear(@Nullable @Name String name) {
        return new Pear(name);
    }
}

// Component不需要特別的處理
@Singleton
@Component(modules = {ProjectModule.class})
public interface FruitComponent {
    FruitShop inject();
}

public class FruitShop {

    @Inject @FruitType("pear")
    Fruit pear;

    // 打印出Pear的名字
    public String createFruit() {
        return pear.get().name();
    }

}

public class Main {
    public static void main(String[] args) {
        // 通過projectModule構造傳遞參數
        FruitShop fruitShop = DaggerFruitComponent
                .builder()
                .projectModule(new ProjectModule("cus_Pear"))
                .build()
                .inject();
        System.out.println(fruitShop.createFruit());
    }
}

上面代碼中,通過ProjectModule的構建函數傳入了一個String對象參數,并最后用于構造Pear對象,最終會打印cus_Pear。對于這種在依賴關系圖中需要外部傳入參數的情況,可以使用@BindInstance來進行優化。優化之后的代碼如下:

// ProjectModule中不需要另外聲明構造函數
@Module
public class ProjectModule {

    @Provides @FruitType("pear")
    public Fruit providerPear(@Name String name) {
        return new Pear(name);
    }
}

@Component(modules = {ProjectModule.class})
public interface FruitComponent {
    FruitShop inject();

    // 通過Component.Builder并使用BindsInstance提供依賴需要參數
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder cusPearName(@Name String name);
        FruitComponent build();
    }
}

public class Main {
    public static void main(String[] args) {
        // 使用builder中的cusPearName方法傳入參數
        FruitShop fruitShop = DaggerFruitComponent
                .builder()
                .cusPearName("cus_Pear")
                .build()
                .inject();
        System.out.println(fruitShop.createFruit());
    }
}

與第一種方法不同,這種方法并不需要使用Module的帶參數構造方法來傳遞依賴所需的參數,而是通過Component構造時候在build的過程中通過cusPearName方法傳入依賴對象,邏輯更加清晰,并且減少了Module的復雜度。
使用@BindInstance注解的方法,如果參數沒有標記為@Nullable則這個方法必須要調用,否則會報java.lang.IllegalStateException: java.lang.String must be set。傳入參數必須為非null,否則會報java.lang.NullPointerException at dagger.internal.Preconditions.checkNotNull(Preconditions.java:33)。如果這個參數是可選,則必須聲明為nullable,如下:

@Module
public class ProjectModule {

    @Provides @FruitType("pear")
    public Fruit providerPear(@Nullable @Name String name) {
        return new Pear(name);
    }
}

@Component(modules = {ProjectModule.class})
public interface FruitComponent {
    FruitShop inject();

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder cusPearName(@Nullable @Name String name);
        FruitComponent build();
    }
}

在實際項目中,應該盡量使用@BindInstance,而不是帶參數構造函數的module。

@BindsOptionalOf

@MapKey

@Multibinds

@IntoMap @IntoSet @ElementsIntoSet

@StringKey @IntKey @LongKey @ClassKey

Dagger2的缺點

  • 修改完相關依賴之后必須Rebuild才能生效
  • 代碼檢索變得相對困難,對于接口或者抽象類沒辦法直觀看到具體生成的是哪個對象
    Kodein
  • 編寫Dagger代碼時需要關注比較多的規則約束,且不太容易記憶(例如Component中的方法要求,以及Builder里的方法要求等)

最后

Dagger2是非常棒的依賴注入器,但是Dagger2使用存在上述的一些缺點,所以建議僅在如架構關系之類的關鍵且依賴關系相對不經常修改的地方使用,不建議在項目中大范圍使用。

例子代碼下載:

本文的代碼可以在github中下載:https://github.com/shenguojun/DaggerExample

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容