dagger2用戶指南:官方文檔翻譯

原文地址

https://google.github.io/dagger/users-guide

適合使用過一段時間Dagger2的人看。
可能會用到的示例。下載
官方github上的示例鏈接:https://github.com/google/dagger/tree/master/examples/simple/src/main/java/coffee
有不對或者不合適的地方請指出 以免誤人子弟。

Dagger2用戶指南

應用中最好的類是那些真正做事情的類。比如:BarcodeDecoder (條碼解碼器),KoopaPhysicsEngine (koopa 物理引擎) 和 AudioStreamer (音頻流)。這些類會可能會依賴于其他類。
比如:BarcodeCameraFinder (條形碼相機取景器),DefaultPhysicsEngine (默認的物理引擎) 或者 HttpStreamer

與之相對的,應用中最差的類,是那些占用空間卻不做事情的。
比如:BarcodeDecoderFactory (條碼解碼器工廠)、CameraServiceLoader (相機服務加載器) 和 MutableContextWrapper (不定上下文包裝器)。這些類并沒有做一些實際的事情,只是起到將有用的類鏈接起來的作用。

Dagger是這些什么什么工廠類之類的替代者,Dagger實現了依賴注入的設計模式,卻不用關心使用依賴注入所需要的各種模板(Dagger框架已經做好了這一步)。這樣我們就可以只需要將目光聚焦在真正做事情的類上。聲明這些類需要的依賴,然后為這些類提供依賴,接下來就可以運行程序了。

通過構建一個標準的Java注解,就可以很方便的測試類,不需要通過一堆樣板將 RpcCreditCardService 轉換成 FakeCreditCardService

依賴注入不僅僅是為了測試,它使得創建高復用性,高可替換性的模塊變得更容易。這樣就可以在不同的應用之間共享同一個 AuthenticationModule 。(譯者附加:只要一個類滿足單一職責原則,那么這個類就可以在不同地方多次使用,如果這個類還滿足接口隔離原則與依賴倒置原則,那么在使用該類的地方,就可以方便的替換成另一個類。) 這樣你就可以在開發環境中使用 DevLoggingModule ,在生產環境中使用 ProdLoggingModule,根據不同的環境使用不同的類來完成正確的事情。

為什么Dagger2是與眾不同的

依賴注入框架已經存在了很多年,可以給各種各樣的API提供配置和注入功能。那么為什么重新發明輪子(我對畫輪子的理解:對已經存在的實現某種功能的框架,根據其能完成的功能,重新創建/設計框架)?Dagger2是第一個通過生成的代碼實現完整的堆棧的框架。原理是生成一些模仿用戶可能會手寫的代碼,使得依賴注入變得簡單、可追蹤、高效。有關更多設計的背景,查看Gregory Kick演講幻燈片

使用Dagger2

我們通過構建一個咖啡機來演示依賴注入與Dagger。有關可以編譯與運行的完整示例代碼,查看 咖啡示例,或者直接下載

聲明依賴

Dagger 為我們應用程序中的類構建實例,并且滿足這些實例的依賴(為這些實例提供依賴)。使用javax.inject.Inject 注解來標識它感興趣的構造器和字段。(使用 @Inject 注解標識構造方法或成員變量)。

使用 @Inject 注解標注需要使用Dagger來創建實例的類的構造方法。當需要創建一個新的實例時,Dagger會獲得需求的參數值,并且調用這個構造方法。(構造方法中的參數,Dagger獲得后會自動傳入)。

public class Thermosiphon implements Pump {
    private final Heater heater;

    /**
     * 使用 @Inject 注解標注了構造方法
     * @param heater 參數Dagger獲得后會自動傳入
     */
    @Inject
    public Thermosiphon(Heater heater) {
        this.heater = heater;
    }
}

Dagger 能夠準確的注入屬性。在下面的例子中,它獲得一個 Heater 實例并賦值給 heater 成員變量,也會獲得一個 Pump 實例賦值給 pump 成員變量。

public class CoffeeMaker {

    /**
     * 使用@Inject注解標注了屬性
     */
    @Inject 
    Heater heater;

    @Inject
    Pump pump; 
}

如果你的類有 @Inject 注解屬性,但是沒有使用 @Inject 注解構造方法,如果有需求的話,Dagger會注入這些屬性,但是不會創建新的對象(暫時沒懂)。如果想讓Dagger能夠創建對象,可以添加一個被 @Inject 注解標注的無參數的構造方法。

Dagger 也支持方法注入,盡管構造方法注入或屬性注入是典型的首選。

沒有 @Inject 的類不能通過Dagger構建對象。

滿足依賴(上面是聲明,這里就是注入了)

默認情況下,Dagger通過構造一個如上所述的請求類型的實例來滿足每個依賴關系。當你需要一個 CoffeeMaker ,Dagger 會調用 new CoffeeMaker() 并且設置(set) CoffeeMaker 的可注入字段,來獲得一個實例。

但是 @Inject 不是什么地方都能用。

  • 接口不能被構造。(這個應該都ok)
  • 第三方的類不能被注解。
  • 必須配置可配置對象。(啥意思?)

對于 @Inject 不足或者笨拙的情況(上述三種示例),使用 @Provides 注解標注方法來提供依賴(滿足依賴)。方法的返回值類型定義了它滿足的依賴關系。(比如類A中有個屬性B需要一個方法來提供依賴,這個方法的返回值就應該是B的類型)

例如, 當需要一個 Heater 對象時,那么就會調用 provideHeater()

@Module(includes = PumpModule.class)
public class DripCoffeeModule {
    @Provides static Heater provideHeater(){
        return new ElectricHeater();
    }
}

@Provides 標注的方法也可能存在自己的依賴關系,如果需要 Pump 對象,就按下面代碼寫。

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

所有的 @Provides 方法必須屬于一個module,這個module就是一個被 @Module 注解標注的類。

/**
 * 一個普通的類,被 @Module 修飾,就變成了 Dagger框架中的 module
 */
@Module
class DripCoffeeModule {
  @Provides 
  @Singleton
  static Heater provideHeater() {
    return new ElectricHeater();
  }

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

按照慣例,@Provides 修飾的方法的方法名,以 provide 為前綴。 @Module 修飾的類的類名,以 Module 為后綴。

構建圖(一直在說對象圖,終于能看到到底是啥了,是數據結構中的圖嗎?)

@Inject 注解和 @Provides 注解標注的類會組成一個對象圖,類與類之間通過依賴關系關聯彼此。調用類似于(應用程序的 main 方法或Android的 Application 都是程序的入口)的代碼,通過明確定義的根集合來訪問該圖形(對象圖)(根集合是什么?)。在Dagger2中,這個集合由一個接口定義,該接口中的方法沒有參數,并且返回值為我們所需的類型(也就是所需的依賴的類型)。通過將 @Component 注解標注于此類接口,并將模塊類型(module的類型)傳遞給modules參數(是 @Component 注解的參數),Dagger2就能夠完整的幫助我們構建根集合。

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

生成的實現類具有同樣的格式的名字,以 Dagger 為前綴,后面拼接上該接口的接口名。通過調用生成類的 builder() 方法,可以獲得一個 builder 實例,使用獲得的這個 builder 實例可以設置依賴,然后調用 build() 方法創建一個新的實例(這個才是我們真正想要的依賴)。

CoffeeShop coffeeShop = 
    DaggerCoffeeShop.builder()//建造者模式?獲得 builder
    .dripCoffeeModule(new DripCoffeeModule())//調用builder的方法設置依賴
    .build();//調用build方法完成構建

注意:如果你的 @Component 標注的類,不是最頂級的類(就是內部類唄),那么生成的 component 組件的名稱會包含該類的外部類的名稱,通過下劃線拼接將它們拼接起來。示例如下:

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

會生成一個叫做 DaggerFoo_Bar_BazComponentcomponent 組件。

任何具有可訪問的默認構造方法的 module 組件都可以被省略,因為如果沒有設置(不將 module 對象設置到 builder里),builder (建造器)將自動構建實例。如果 module 模塊中,被 @Provides 注解標注的方法都是靜態的,那么 @Component 注解標注的 component 組件(是一個接口)的實現不需要創建實例。如果所有的依賴關系,都可以在不創建依賴實例的情況下構建,那么(Dagger)生成的實現( component 接口的實現)也會有一個 create() 方法,通過該方法可以獲得一個新的實例,而略過 builder (不需要使用 builder)。

//DaggerCoffeeShop就是Dagger2為我們生成的 component 組件的實現類
//正常情況下,是需要調用 builder() 然后將 module 都設置到 建造器中的(builder)
//但是上面所述情況就可以跳過 builder ,跳過設置 module 的步驟,直接使用 create() 方法即可
CoffeeShop coffeeShop = DaggerCoffeeShop.create();

CoffeeShopCoffeeApp 類的一個內部接口,被 @Component 注解標注。這樣,CoffeeApp類,就可以簡單的使用Dagger為我們生成的 CoffeeShop 的實現類對象來獲得完全注入的 CoffeeMaker

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

    public static void main(String[] args) {
        CoffeeShop coffeeShop = DaggerCoffeeApp_CoffeeShop.create();
        //maker()后會得到CoffeeMaker對象,然后調用CoffeeMaker對象的brew()方法
        coffeeShop.maker().brew();
    }
}

現在已經構建好了對象圖,也將切入點注入(就是注入了依賴),運行程序如下所示:


image

關于圖中的綁定

上述的例子展示了怎么使用一些更典型的綁定( binding )構建一個 component 組件,但是也有多種機制可以為對象圖提供綁定功能。以下可用作依賴項,也可用于生成格式良好的組件:

  • 那些直接被 @Component 注解的 modules 屬性引用的 @Module 模塊中被 @Provides 標注的方法 或者 傳遞給 @Module 注解的 includes 屬性。(這句話是真的沒看懂,原文在下面)
  • Those declared by @Provides methods within a @Module referenced directly by @Component.modules or transitively via @Module.includes
  • 任何一個被 @Inject 注解標注構造方法的類型,不管該類型是否被一個 @Scope 注解標注,都會與一個 component 組件的 域(scope)匹配。
  • 聯系組件依賴關系組件提供方式
  • 組件本身
  • Unqualified builders for any included subcomponent(任何被包含的子組件不適合構建者?)
  • Provider 或者 Lazy 用于上述任何綁定的包裝器
  • 上述任何綁定的 LazyProvider(例子:Provider<Lazy<CoffeeMaker>>)(????)
  • 一個可以用于任意類型的 MembersInjector

Singletons and Scoped Bindings (單例與域的綁定)

使用 @Singleton 注解標注一個 @Provides 的方法或者可注入的類。該對象圖將會為它所有的客戶端提供一個該類型的單例實例。(那么這個圖的范圍是什么?是 component 組件嗎?)

/**
 * 如果下載了上述的demo項目,可以嘗試把 @Singleton去掉后運行程序
 * 會發現輸出日志中會少一行:=> => pumping => =>
 * 是因為在輸出的時候做了一個判斷,判斷 heater.isHot() 是否為true
 * 沒輸出說明不是true
 * 但是會發現輸出了:~ ~ ~ heating ~ ~ ~,會發現存在:this.heating = true
 * 明明設置為true了,判斷的時候為什么不通過?
 * 可以嘗試在 `Thermosiphon.pump()` 中輸出 heater
 * 在 `ElectricHeater.on()` 中輸出 this
 * 會發現是兩個對象
 * 在使用Dagger2時要尤其注意Scope
 */
@Provides 
@Singleton 
static Heater provideHeater() {
  return new ElectricHeater();
}

標注在可注入類上的注解 @Singleton 也可標注在 文檔(documentation)上。它提醒潛在的維護者這個類可能被多個線程共享。(就是表示這個類會被多個地方使用,希望這些地方使用這個類的時候,用的都是同一個對象。把 “這些地方” 就可以理解成一個域,表示一個范圍,也就是在這個范圍中,這個類只有一個對象,也就是單例。一定要注意范圍。在這個范圍中單例了,在其他范圍中也可能會有多個。范圍就是域,就是 Scope)

@Singleton
class CoffeeMaker {
  ...
}

由于 Dagger2 將對象圖中的范圍實例與 component 組件的實現類對象相關聯,那么組件需要聲明它所表示的范圍。比如,沒有必要在同一個 component 上同時標注 @Singleton@RequestScoped 注解,因為這兩個注解所代表的域具有不同的生命周期,所以在使用這兩個注解時,它們標注的 component 的聲明周期也應該是不同的。要想將一個 component組件與一個給定的域聯系起來,很簡單,只需要在 component 接口上使用該域(@Singleton 只是一種,還可以是各種自定義注解)注解標注即可。

/*
 * @Singleton 標注了接口CoffeeShop
 * 表示這個 component 與 @Singleton 聯系起來
 * 那么在 DripCoffeeModule.class 中被 @Singleton 標注的 provide 方法
 * 就會提供在此范圍內的單例對象
 */
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}

component 組件也可能被多個域注解標注。這表示它們(域注解)都是同一范圍的別名(實際就代表都是一個域),所以 component 組件就會包含綁定在它上的域(域注解)中的任意一個對象。(@A,@B都標注在一個 DemoComponent 上,那么在 module 中的 @Provides 注解標注的方法(還需要一個域注解標注),標注該方法上的域注解是 @A 還是 @B 都屬于同一個范圍。都可以通過 DaggerDemoComponent (Dagger2為我們生成的實現類) 獲得。)

Reusable scope (可重用的范圍)

有時候你想限制被 @Inject 注解標注的類的實例化次數,或者被 @Provides 注解標注的方法的調用次數,但是你不需要保證在任何特定 component 組件 或 subcomponent 子組件的生命周期中使用完全相同的實例對象。這在Android環境中很有用,因為分配的成本可能很高(這個成本指的是?)。

對于這些綁定來說,你可以使用 @Reusable 域。 @Reusable 域注解,與其他的域有一些不同,它不與任何單例的 component 組件產生聯系。相反,實際使用綁定的 component 組件會緩存被返回的或實例化的對象(沒懂)。

那意味著如果你在 component 組件初始化一個被 @Reusable 綁定的 module 模塊,但是實際上只有一個 subcomponent 子組件使用這個綁定,那么只有這個子組件會緩存綁定的對象(這個緩存,指的是持有引用?)。如果兩個不共享父級(父接口)的 subcomponent 子組件都使用這個綁定,那么他們中的每一個(就是每一個子組件)都會緩存一份屬于自己的對象(子組件A緩存對象a,子組件B緩存對象b,a、b是同一類型的不同對象)。如果一個 component 的父級(父接口)已經緩存好了這個對象,那么 subcomponent 子組件會使用它( subcomponent 會直接使用 component 緩存好的對象 )。

無法保證 component 組件只調用綁定一次,因此將 @Reusable 應用于返回可變對象的綁定,或者應用在引用相同實例的重要的對象上是危險的。將 @Reusable 應用在不可變的對象上才是安全的,如果你不在乎它們(不變的對象)被分配了多少次,那么你可以不指定范圍(域)。

//我們使用多少范圍注解并不重要,但是別浪費他們。
@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() {}
}

Releasable references (可釋放的引用)

反對:此功能已啟用,計劃在2018年7月刪除。
ok,少翻譯一大塊,開心的不行。
算了還是了解一下吧。

當綁定的時候使用了域注解,那就意味著這個 component 組件對象持有一個綁定對象的引用,直到這個 component 對象被垃圾回收機制回收。在安卓敏感的內存環境中,當內存不足時,你也許想讓沒有正在使用的域對象被垃圾回收機制處理。

在那種情況下,你可以定義一個域(scope) ,并且使用 @CanReleaseReferences 注解這個 scope

@Documented
@Retention(RUNTIME)
//這樣,該域中(MyScope)的對象,如果不是正在使用中,當內存不足時就會被釋放
@CanReleaseReferences
@Scope
public @interface MyScope {}

當你確定允許當某個域中的對象當前未被其他對象使用時,可以被垃圾回收機制回收,那么你可以向你的域中注入一個 ReleasableReferenceManager 對象,然后調用這個對象的 releaseStrongReferences() 方法,這個方法會使得 component組件持有一個該對象的 WeakReference 而不是 strong reference。(弱引用:垃圾回收機制每次都會回收掉弱引用的對象。強引用:強引用指向的對象永遠不會被垃圾回收機制回收,即時內存不足。)Java強弱軟虛四種引用類型

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

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

如果你確定內存不足情景已經過去(就是又有內存了),那么你可以為任意已緩存的在低內存時期調用了 releaseStrongReferences() 后還沒有被垃圾回收機制回收的對象恢復成強引用。(這個定語是真的長。。。。)

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

Lazy injections (懶注入)

有時你需要一個被懶加載的對象。對于任意的綁定 T (泛型開始),你可以創建一個在第一次調用 Lazy<T>'s get() 方法時,才實例化對象的 Lazy<T> 。如果 T 是一個單例的類型,那么 Lazy<T> 會在對象圖 ObjectGraph 中需要 T 的任意一個地方注入同一個實例。否則,每一個等待注入的地方都會獲得一個它自己的 Lazy<T> 實例(A 中的 Lazy<T> 和 B 中的 Lazy<T> 是同一個類型的不同對象)。不管怎樣,對已給定的 Lazy<T> 的后續調用都將返回同樣的底層的 T 實例(第一次調用get()時創建T的對象,后續調用get()返回的都是前面創建的同一個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 injections ( Provider 方式的注入)

有時你需要返回得到多個實例,而不是僅僅注入一個單例的對象。當你有幾個選項(工廠,建造者等)時,一個選項要注入一個 Provider<T> 而不是只注入一個 T。每次調用 Provider<T>get() 方法時,Provider 都會調用 T 的綁定邏輯。如果這個綁定邏輯是一個被 @Inject 標注的構造方法,那么再次調用 T 的綁定邏輯就意味著再次調用 T 的構造方法,這時,就創建了一個 T 的新對象,但是如果綁定規則是一個被 @Provides 標注的方法,那么就不能夠保證會再次新建一個對象(方法的返回值是我們任意寫的,如果我們沒寫類似 new T() 的代碼,那么自然就不會創建新的 T)。

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();
      ...
    }
  }
}

譯者附加(可能沒啥卵用,別噴我):有的時候我們可能會遇到對象的轉換,將 List<AAAResponse> 中的數據設置到 List<AAA> 中,可能就會出現上述的循環代碼,此時創建 AAA 對象的過程就可以通過多次調用 Provider<AAA>get() 方法來實現。就不用再寫 new AAA() 了。當然更好的是注入 Provider<AAASuper> (AAASuperAAA 的超類),這樣如果改了需求要將數據轉換到 List<BBB> 中,只需要更改注入的對象即可,不需要改動當前類的任何代碼。

筆記:注入的 Provider<T> 可能創建了難以理解的代碼,也可能是在對象圖中設計了錯誤的范圍或錯誤的對象結構。通常你想使用 factory 或者是 Lazy<T> 或者是更改你代碼的生命周期和代碼結構,使得你能夠直接注入一個 T。 但是,在某些情況下,注入 Provider<T> 可以節省生命(啥意思?)。一個普遍的用途是當你必須使用不符合對象自然生命周期的傳統框架時(就用唄就?)。(這個括號也是原文中的內容)(例子:servlets 被設計成單例模式,但是僅僅在 request 的上下文中有效。)

(這里是我加的:) Servlet 是javaweb中處理請求的模塊,運行在java客戶端的java程序叫做 applet ,為客戶端提供支持的叫 serverservlet 就是 serverapplet 的組合詞。在javaweb中,如果不做特殊才處理(設置servlet的級別),只有當請求第一次發生時,才會創建對應的servlet對象,下次再訪問同樣的請求,不會再創建新的。也就是說如果 request 不發生,則 servlet 便不會做什么事情(正常情況下)。

Qualifiers (限定)

有時單獨的類型不足以識別依賴關系。例如,精致的咖啡機在加熱不同的物品時應該使用不同的加熱器。

在這種情況下,我們添加了一個限定注解。任何一個被 @Qualifier 標注的注解(包含自定義注解),都屬于限定注解。這里有一個包含在 javax.inject 中的限定注解 @Named 的聲明。

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

你可以創建你自己的限定注解,或者直接使用 @Named 。使用限定注解標注在感興趣的屬性和參數上。類型和限定注解,都會被用來表示依賴項。

class ExpensiveCoffeeMaker {
  //如果是不同類型的依賴項,只需要使用一個 `@Inject` 即可
  //這里依賴了兩個同類型的對象,那么只根據類型區分就不滿足了,可以使用限定注解做區分
  @Inject @Named("water") Heater waterHeater;
  @Inject @Named("hot plate") Heater hotPlateHeater;
  ...
}

通過標注一致的被 @Provides 方法,提供限定的值。

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

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

依賴關系可能不會具有多個限定注解。(對于一個依賴項而已,基本上一個限定注解標注就夠了)

Optional bindings (可選綁定)

如果你想要 component 中的某些依賴項沒有被綁定的情況下,依然能夠正常運行,那么可以在 module 中添加一個 @BindsOptionalOf 標注的抽象方法:

@BindsOptionalOf 
abstract CoffeeCozy optionalCozy();

這意味著被 @Inject 標注的構造方法、成員變量和被 @Provides 標注的方法可以依賴于 @Optional<CoffeeCozy> 對象。如果在 component 組件中有一個 CoffeeCozy 的綁定, 則將顯示 Optional ,如果沒有 CoffeeCozy 的綁定,那么 Optional將不存在。

特殊的,你可以注入如下的任意一個。

  • Optional<CoffeeCozy> (除非 CoffeeCozy@Nullable 綁定)
  • Optional<Provider<CoffeeCozy>>
  • Optional<Lazy<CoffeeCozy>>
  • Optional<Provider<Lazy<CoffeeCozy>>>

(也是原文中的內容:)(你可以注入一個 Provider 或者 Lazy 或者 Provider<Lazy> 不過沒啥用)

和下面的一個道理

  • List<String>
  • List<List<String>>
  • List<Hashmap<String,List<String>>>

如果 CoffeeCozy 有綁定,并且綁定是 @Nullable ,那么注入 Optional<CoffeeCozy> 是一個編譯期錯誤,因為 Optional不能包含 null 。你可以一直注入其他的格式,因為 ProviderLazyget() 方法能夠返回 null

在一個 component 組件中的可選擇的隱藏的綁定,如果 subcomponent 子組件包含基礎類型的綁定,那么可以在 subcomponent 子組件中顯示。

可以使用 Guava’s Optional 或者 Java 8’s Optional

Binding Instances (綁定實例)

通常在構建 component 時,你會得到可用的數據。例如,假設你有一個使用命令行參數的應用程序,你也許想在你的 component 組件中綁定那些參數。

也許你的應用程序只需要一個參數來表示你想要注入的用戶名( @UserName String )。你可以在 componentbuilder 中添加一個 @BindsInstance 標注的方法使得實例可以被注入到這個 component 組件中。

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

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

然后你的應用就會像這樣:

public static void main(String[] args) {
  if (args.length > 1) { exit(1); }
  App app = DaggerAppComponent
      .builder()
      .userName(args[0])//傳入參數,與傳入module類似
      .build()
      .app();
  app.run();
}

在上述例子中,在 component 組件中注入 @UserName String 將在調用次方法時,使用提供給 Builder 的實例。在構建 component 組件之前,必須先調用所有的 @BindsInstance 標注的方法,傳遞非空值(下面 @Nullable 綁定的除外)。

如果一個被 @BindsInstance 標注的方法中的參數被 @Nullable 標注,那么這個綁定就會被認為是可空的,就像 @Provides 標注的方法也是可空的一樣,注入點也一定標記了 @Nullable,那么 null 就是該幫綁定的一個可接收的值。此外, Builder 的用戶可以不調用這個方法,那么 component 組件會認為這個實例是一個null值。

@BindsInstance 標注的方法應該優先使用構造函數參數編寫 @Module 并且立即提供這些值。

Compile-time Validation (編譯期驗證)

Dagger annotation processor (注解處理器) 是精確的,如果任何一個綁定是無效或者不完整的,那么就會導致編譯期錯誤。例如,下面的 module 被一個沒有綁定 Executorcomponent 實例化:

@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.

通過在 component 中的任意一個 module 中添加一個 Executor@Provides 標注的方法(該方法返回一個 Executor 的對象)來修復錯誤。雖然 @Inject@Module@Provides 注解是單獨驗證的,但是綁定與綁定之間的關系的驗證都發生在 @Component 級別。Dagger 1 嚴格依賴與 @Module 級驗證(可能存在運行時執行反射行為),但是 Dagger 2 不需要這樣的驗證(以及在 @Module 上附帶的配置參數),支持完整的圖形驗證。

Compile-time Code Generation (編譯器的代碼生成)

Dagger 的注解處理器也會生成名字類似于 CoffeeMaker_Factory.java 或者 CoffeeMaker_MembersInjector.java 這樣的資源文件。這些文件是Dagger實現的細節。你不應該直接使用它們,盡管在進行 debug 調試它們時會很方便。你唯一應該在代碼里引用的是那些根據你的 component 生成的前綴為 Dagger 的類型。

Using Dagger In Your Build

你需要在你的應用程序運行時包含 dagger-2.X.jar 。為了觸發代碼生成器你需要在編譯期構建時包含 dagger-compiler-2.X.jar 。查看更多信息 讀我

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

推薦閱讀更多精彩內容