Dagger2 User's Guide

文章翻譯自Dagger官網(wǎng),翻譯水平有限,見諒。

引入

引用官網(wǎng)的引入說明,上面的部分都好理解,就是簡單的compile,然后說如果Studio是2.2的話,推薦去升級Studio。
如果你和Databinding一起用的話,那么需要看這個Issue 306
這樣就可以使用Dagger。

使用Dagger

我們將通過一個coffee maker項目來演示依賴注入和Dagger,完整的可以編譯和運行的一個簡單的例子,在這里

聲明依賴

Dagger 構(gòu)建你項目中類的實例,并且滿足他們的依賴。它使用javax.inject.Inject這個注解去標識想要注入的構(gòu)造函數(shù)和屬性。

用@Inject去標注一個Dagger應(yīng)該創(chuàng)建實例的類的構(gòu)造函數(shù)。當一個請求對應(yīng)的實例的時候,Dagger將獲得請求的參數(shù)值并且調(diào)用指定類的構(gòu)造函數(shù)。

class Thermosiphon implements Pump {
    private final Heater heater;

    @Inject
    Thermosiphon(Heater heater) {
        this.heater = heater;
    }

    ...
}

Dagger能夠直接注入成員變量,在這個例子中,它獲得一個Heater實例給heaters成員變量,并且獲得一個Pump實例給pump成員變量。

class CoffeeMaker {
    @Inject Heater heater;
    @Inject Pump pump;

    ...
}

如果你的類中有@Inject注解的成員變量,但是沒有@Inject注解的構(gòu)造函數(shù),Dagger將要注入被請求的成員變量,但是不會創(chuàng)建新的實例。 通過添加一個使用@Inject注解的空參構(gòu)造函數(shù)可以是得Dagger正常的創(chuàng)建實例。

Dagger 同樣支持方法注入,盡管構(gòu)造函數(shù)和成員變量類型的注入更受歡迎。

沒有被@Inject注解的類是不能通過Dagger構(gòu)造的。

滿足依賴

默認情況下,Dagger能夠通過以上的請求方式構(gòu)建每一條依賴對象的實例。當你想要獲取一個CoffeeMaker,Dagger將通過 new CaffeeMaker() 并且設(shè)置它的可注入的成員變量。

但是@Inject也有缺陷:

  • Interfaces 不能夠構(gòu)建

  • 第三方類不能夠被注解

  • 可配置的類必須是配置的

例如,當一個Heaters實例被請求的時候會調(diào)用 provideHeater() 這個方法:

@Provides static Heater provideHeater() {
    return new ElectricHeater();
}

通過@Provides這個注解使得擁有他們自己的依賴成為可能(這是什么翻譯,手動捂臉)。
這個是返回一個 Thermosiphon 當一個Pump 被請求的時候:

@Provides static Pump providePump(Thermosiphon pump) {
    return pump;
}

所有的@Provides注解的方法必須屬于一個Module,這里是一個通過@Module注解的類:

@Module
class DripCoffeeModule {
    @Provides static Heater provideHeater() {
        return new ElectricHeater();
    }

    @Provides static Pump providePump(Thermosiphon pump) {
        return pump;
    }
}

約定,使用@Provides注解的方法使用provide做方法名的前綴,使用@Module注解的類使用module做類名的后綴。

建立依賴圖

這些使用@Inject和@Provides注解的類通過它們的依賴連接在一起。調(diào)用項目的main方法或者是一個安卓程序通過一個明確定義的樹狀圖。這Dagger2中這個集合(前面說的樹狀圖)是通過一個擁有空參的方法的接口并且返回要求的類型。通過提供@Component注解給這樣額接口并且提供moduley一個@module的參數(shù)。之后Dagger2能夠生成一個具體的關(guān)系。

我再用白話簡單說一下,主要意思就是為了使得被@Inject和@Provides注解的類能夠相互依賴,可以使用@Component注解并且設(shè)置module的value,這樣@Inject的注解就能和@Provides的注解相互依賴了。

這里其實應(yīng)該可以注意到的就是@Inject注解的成員變量對應(yīng)的構(gòu)造函數(shù)必須也得是@Inject注解過的,但是@Inject有的情況下是無法使用的上文也說了,可以是用@Provides注解解決這個問題,但是這又出現(xiàn)一個新的問題,@Inject注解怎么和@Provides注解進行溝通?答案是使用@Component。

@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
     CoffeeMaker maker();
}

這個接口是實例擁有和接口相同的類名,不過有Dagger前綴。可以通過這個實例調(diào)用 builer() 方法來獲得一個實例對象,設(shè)置對應(yīng)module,然后通過 build()方法來返回一個實例對象。

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
        .dripCoffeeModule(new DripCoffeeModule())
        .build();

注:如果你的@Component注解的類不是頂級類型的話,這個生成的Component的名字將包含所有的閉合類型的名字,通過下劃線拼接,例如下面你的代碼:

class Foo {
    static class Bar {
        @Component
        interface BazComponent {}
    }
}

將會生成一個DaggerFoo_Bar_BazComponent 命名的Component。

任何一個可以獲得默認構(gòu)造函數(shù)的module會被忽略作為Builder 將會創(chuàng)建實例對象想,如果沒有設(shè)置的話。并且所有的module中被@Provides注解的方法都是static的。這個實例根本不需要一個具體的對象。如果所有的實例可以創(chuàng)建通過用戶生成一個依賴對象的話,那么構(gòu)建實例也將會有一個create()方法用來獲取一個新的實例對象,不用通過使用builder。

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

現(xiàn)在我們的CofferApp能夠簡單的使用Dagger生成一個CoffeeShop的實例去獲得一個CoffeeMaker。

public class CoffeeApp {
    public static void main(String[] args) {
        CoffeeShop coffeeShop = DaggerCoffeeShop.create();
        coffeeShop.maker().brew();
    }
}

現(xiàn)在依賴關(guān)系被構(gòu)建出來了,并且所有的實例對象都被注入了,我們能夠愉快的運行這個項目:

$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P

綁定依賴圖

上面的例子展示了怎樣創(chuàng)建一個component通過一些類型綁定,但是其實是有多種機制去構(gòu)建一個依賴圖的。下面的方法都是有效的去構(gòu)建一個合格的component:

  • 這些包含被@Provides聲明的方法 @Module注解的類可以直接被@Component.modules直接引用,也可以間接的通過@Module.includes引用。

  • 任何被@Inject注解的構(gòu)造函數(shù)是不在作用域內(nèi)的,或者擁有一個@Scope注解來匹配一個Component范圍。

  • component 依賴的提供方法。

  • component 他自己

  • 包含subcomponent的不限定的builders

  • Provider或者Lazy包裝的以上的綁定

  • Provider的Lazy的以上的綁定例如 Provider<Lazy<CoffeeMaker>>

  • 任何類型的MemberInjector.

單例和范圍綁定

通過使用@Provides和@Singleton注解方法或者可以注入的類,這種依賴關(guān)系圖將使用一個單例實例提供給所有的客戶端。

@Provides @Singleton static Heater provideHeater() {
    return new ElectricHeater();
}

這個@Singleton注解在一個可注入的類上也作為記錄提供。它提醒著使用者這個類可能會在多線程使用。

@Singleton
class CoffeeMaker {
    ...
}

自從Dagger2連接范圍內(nèi)的實例通過componet的實例對象,這些Components他們自己需要聲明他們感興趣的范圍。例如,在同一個Component中是不可能同時有一個@Singleton綁定和一個@RequestScoped綁定,因為這兩個范圍用于不同的生命周期并且因此必須放在聲明周期不同的Components中,為了聲明一個關(guān)聯(lián)已經(jīng)提供的scope的Component,簡單的提供范圍注解到這個Component接口中就可以。

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
    CoffeeMaker maker();
}

Components可能會擁有多種范圍注解。這些聲明的別名都是相同的,所以Component可能包含他聲明的任意一個范圍類型。

復(fù)用范圍

有時候你可能會限制使用@Inject注解的構(gòu)造函數(shù)的實例化或者@Provides注解方法被調(diào)用的數(shù)量,但是你并不需要保證這些需要的實例是在特別的Component或者子Component整個生命周期被使用,在Android 環(huán)境中這會是有用的。或者其他的配置是昂貴的系統(tǒng)中。

為了這些綁定,你可以提供@Resuable 范圍。 @Resuable范圍綁定,不像是其他的范圍綁定,是不會和其他的任何單獨的Component關(guān)聯(lián)的。反而 每一個compoent實際上能夠用這些綁定通過返回的緩存或者是實例的具體類。

這就意味著,如果你創(chuàng)建一個module利用了@Resuable綁定在一個Component中,但是只有一個子Component實際上使用了這個這個綁定。然后只有這個子Component緩存了這個綁定的實例。如果兩個子Component沒有通過綁定共享同一個父Component,每一個都將會緩存他們自己的實例,如果一個component的父Component已經(jīng)緩存了這個類,那么它的子Component將會使用這個對象。

這里沒有任何的保證Component會只調(diào)用這種綁定一次,所以使用@Resuable去綁定并且返回易變的類,或者是涉及到相同實例的類是危險的。它是安全的對于不變的類就是如果你不關(guān)心他們被分配多少次。

@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
    @Inject CoffeeScooper() {}
}

@Module
class CashRegisterModule {
    @Provides
    @Reusable // DON'T DO THIS! You do care which register you put your cash in.
                // Use a specific scope instead.
    static CashRegister badIdeaCashRegister() {
         return new CashRegister();
    }
}

@Reusable // DON'T DO THIS! You really do want a new filter each time, so this
      // should be unscoped.
class CoffeeFilter {
     @Inject CoffeeFilter() {}
}

可釋放的引用

當一個綁定使用范圍注解的時候,這就意味著這個Component持有一個實例對象的引用,直到這個Component實例被GC回收(這樣就會有一個問題就是如果Component沒有回收的話,那么它持有的所有引用也無法釋放內(nèi)存)。在內(nèi)存敏感的環(huán)境中,例如Adnroid,你也許會想要讓范圍的類,那些在目前沒有被正在使用類在GC回收的時候刪掉,在項目在有內(nèi)存壓力的情況下。

在這種情況下,你可以定義一個范圍并且使用@CanReleaseReferences注解。

@Documented
@Retention(RUNTIME)
@CanReleaseReferences
@Scope
public @interface MyScope {}

當你決定你將允許這個范圍內(nèi)的持有的類在GC期間是可以被刪除的,如果它們沒有被一些其他類正常的使用,你可以注入一個ReleasableReferenceManager類給你的范圍,并且調(diào)用 releaseStrongReference() 方法,這將使得Component持有一個弱引用對象而不是強引用對象。

@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferenceManager myScopeReferenceManager;

void lowMemory() {
  myScopeReferenceManager.releaseStrongReferences();
}

如果你決定內(nèi)存壓力消退,然后你可以恢復(fù)這些強引用給任何緩存的類-在GC期間還沒有被緩存的實例對象通過調(diào)用restoreStrongReferences()

void highMemory() {
  myScopeReferenceManager.restoreStrongReferences();
}

懶注入

有時你可能會想延遲獲取一個類的實例對象。對于任何的綁定T,你可以是用Lazy<T> 延遲實例化直到第一次調(diào)用Lazy<T>.get()方法,如果 T 是 Singleton ,那么Lazy(T)將是同一個實例在所有的注入的類圖中。另外,每一個注入的位置將擁有它們自己熱Lazy<T>實例。無論,后來的調(diào)用去獲取一個Lazy<T> 的實例將會獲取一個之前的相同的T的實例。

class GrindingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;

  public void brew() {
        while (needsGrinding()) {
        // Grinder created once on first call to .get() and cached.
            lazyGrinder.get().grind();
        }
    }
}

提供者注入

有時你需要多種實例對象返回,而不是單個的實例。當你擁有幾種方案的時候(例如工廠,建造者等),一個選擇是注入一個Provider<T>而不僅僅是 T ,一個Provider<T>調(diào)用構(gòu)建的邏輯在每次.get()方法調(diào)用的時候。如果綁定的邏輯是@Inject的構(gòu)造函數(shù),一個新的實例將被創(chuàng)建,但是一個@Provides注解的方法沒有這樣的保證。

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;

  public void brew(int numberOfPots) {
    ...
        for (int p = 0; p < numberOfPots; p++) {
            maker.addFilter(filterProvider.get()); //new filter every time.
            maker.addCoffee(...);
            maker.percolate();
        ...
         }
     }
}

注: 注入的Provider<T>可能有令人困惑的代碼,并且可能是一個缺少范圍或者是缺少組織的設(shè)計在你的關(guān)系類圖中,通常你會使用工廠或者是一個Lazy<T>或者是重組代碼的機構(gòu)和使用期來能夠注入一個T 。然而注入的Provider<T>能夠?qū)崿F(xiàn),在一些具體的案例中像一個拯救者。一個使用情景就是當你必須使用一個遺留的架構(gòu),這個架構(gòu)不能夠整合你的類的自然生命期。例如servlets是設(shè)計單例的,但是只有當請求特殊的數(shù)據(jù)的情境下是可用的。

限定符

有時類型本身是不足以確定依賴的。例如:一個高級的咖啡機是應(yīng)該是能夠區(qū)分水和加熱的底座。

在這種情況下,我們需要一個限定的注解,這是任意的注解只要它本身被@Qualifier注解就可以,下面的是一個@Named注解的聲明,一個限定的注解在javax.inject中:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

你可以創(chuàng)建你自己的限定注解,或者就是使用@Named,通過注解提供限定信息到感興趣的成員變量或者參數(shù)上。類型和限定注解將一起用于確定依賴。

class ExpensiveCoffeeMaker {
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}

供應(yīng)限定的值通過注解相對應(yīng)的@Provides 方法。

@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
  return new ElectricHeater(70);
}

@Provides @Named("water") static Heater provideWaterHeater() {
  return new ElectricHeater(93);
}

依賴可能不會擁有多種限定注解。

可選綁定

如果你想一個綁定能夠使用盡管一些依賴沒有在這個Component內(nèi)的話,你可以添加一個@BindsOptionalOf注解的方法到一個Module:

@BindsOptionalOf abstract CoffeeCozy optionalCozy();

這就一位置被@Inject注解的構(gòu)造函數(shù)和成員變量還有@Provides注解的方法可以依賴在一個Optional<CoffeeCozy> 類。如果在一個Componet中有這個的一個綁定的話,那么Optional將會被呈現(xiàn),如果這里沒有CoffeeCozy的綁定,那么Optional將會缺少。

特別的你可以注入下面你的任意一個:

  • Optional<CoffeeCozy>

  • Optional<Provider<CoffeeCozy>>

  • Optional<Lazy<CoffeeCozy>>

  • Optional<Provider<Lazy<CoffeeCozy>>>

(當然你也可以注入一個Provider或者Lazy或者Provider包含Lazy,但是這不是很有用)

在Component中一個可選的綁定是缺失的,但是能夠在子的Component中被呈現(xiàn),如果這個子Component包含包含一個綁定潛在的類型

你也以用 Guava’s Optional or Java 8’s Optional.

綁定實例

經(jīng)常你需要有效的數(shù)據(jù)在構(gòu)建Component時。例如 假設(shè)你有一個項目使用命令行參數(shù);你需要綁定這些參數(shù)在你的Component中。

也許你的app需要一個單獨的參數(shù)代表用戶的名字,你或許會使用注解@UserName String 。你可以添加一個唄@BindsInstance 注解了的方法到這個Component builder 去允許這個實例對象被注入到這個Component中。

@Component(modules = AppModule.class)
interface AppComponent {
  App app();

  @Component.Builder
  interface Builder {
        @BindsInstance Builder userName(@UserName String userName);
        AppComponent build();
  }
}

你的app 看起來可能是這樣的:

public static void main(String[] args) {
  if (args.length > 1) { exit(1); }
  App app = DaggerAppComponent
        .builder()
        .userName(args[0])
        .build()
        .app();
        app.run();
 }

在上面的這個例子中,注解@UserName String 在這個Component中將會作為一個實例提供給Builder 當調(diào)用這個方法的時候。
在構(gòu)建Component之前,所有@BindsInstance方法必須被調(diào)用,傳輸一個非空的數(shù)據(jù)。

如果@BindsInstance注解的方法參數(shù)被@Nullable注解,那么這個綁定會被認為是可空的以相同的方式@Provides方法也是可以的。注入的位置也必須用@Nullable 標記,并且Null 是一個允許的數(shù)據(jù)去被綁定,更多的 Builder的使用者可能會遺漏調(diào)用方法,并且Component將會認為這個實例為Null。

@BindsInstance 方法應(yīng)該更偏向?qū)懸粋€@Moudle通過構(gòu)造函數(shù)參數(shù),并且立即提供它們的值。

編譯時檢查

Dagger的注解解釋器是嚴格的并且將會出現(xiàn)編譯錯誤當任何的綁定是無效的或者是無法完成的。例如 ,這Module將被創(chuàng)建在一個Component鐘,這個Component缺少一個綁定的Executor。

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater(Executor executor) {
        return new CpuHeater(executor);
  }
}

當編譯的時候,javac報告缺少綁定。

[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.

解決這個問題通過添加一個@Provides注解的方法用來提供Excutor給任意的Modules在這個Component中。當@Inject、@Module 和@Provides注解是單獨有效的時候,所有的檢查在這個綁定間的關(guān)系在@Component中。Dagger1的依賴在Module層嚴格檢查(這也許會有運行時的反射動作),但是Dagger2省略了這個檢查(并且隨著配置的在@Module中的參數(shù))在一個更好的全局依賴圖中。

編譯時代碼生成

Dagger的注解解析器也許生成這種 CoffeeMaker_Factory.java 的文件或者CoffeeMaker_MembersInjector.java。這些是Dagger實現(xiàn)的細節(jié)。你不必直接使用它們,盡管它們在調(diào)試的時候通過注解很好使用。這里只有一個你更應(yīng)該使用的在你的代碼中國的應(yīng)該是使用Dagger前綴的Component對象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內(nèi)容