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

原文地址

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

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

Dagger2用戶指南

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

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

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

通過構(gòu)建一個(gè)標(biāo)準(zhǔn)的Java注解,就可以很方便的測試類,不需要通過一堆樣板將 RpcCreditCardService 轉(zhuǎn)換成 FakeCreditCardService。

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

為什么Dagger2是與眾不同的

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

使用Dagger2

我們通過構(gòu)建一個(gè)咖啡機(jī)來演示依賴注入與Dagger。有關(guān)可以編譯與運(yùn)行的完整示例代碼,查看 咖啡示例,或者直接下載

聲明依賴

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

使用 @Inject 注解標(biāo)注需要使用Dagger來創(chuàng)建實(shí)例的類的構(gòu)造方法。當(dāng)需要?jiǎng)?chuàng)建一個(gè)新的實(shí)例時(shí),Dagger會(huì)獲得需求的參數(shù)值,并且調(diào)用這個(gè)構(gòu)造方法。(構(gòu)造方法中的參數(shù),Dagger獲得后會(huì)自動(dòng)傳入)。

public class Thermosiphon implements Pump {
    private final Heater heater;

    /**
     * 使用 @Inject 注解標(biāo)注了構(gòu)造方法
     * @param heater 參數(shù)Dagger獲得后會(huì)自動(dòng)傳入
     */
    @Inject
    public Thermosiphon(Heater heater) {
        this.heater = heater;
    }
}

Dagger 能夠準(zhǔn)確的注入屬性。在下面的例子中,它獲得一個(gè) Heater 實(shí)例并賦值給 heater 成員變量,也會(huì)獲得一個(gè) Pump 實(shí)例賦值給 pump 成員變量。

public class CoffeeMaker {

    /**
     * 使用@Inject注解標(biāo)注了屬性
     */
    @Inject 
    Heater heater;

    @Inject
    Pump pump; 
}

如果你的類有 @Inject 注解屬性,但是沒有使用 @Inject 注解構(gòu)造方法,如果有需求的話,Dagger會(huì)注入這些屬性,但是不會(huì)創(chuàng)建新的對象(暫時(shí)沒懂)。如果想讓Dagger能夠創(chuàng)建對象,可以添加一個(gè)被 @Inject 注解標(biāo)注的無參數(shù)的構(gòu)造方法。

Dagger 也支持方法注入,盡管構(gòu)造方法注入或?qū)傩宰⑷胧堑湫偷氖走x。

沒有 @Inject 的類不能通過Dagger構(gòu)建對象。

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

默認(rèn)情況下,Dagger通過構(gòu)造一個(gè)如上所述的請求類型的實(shí)例來滿足每個(gè)依賴關(guān)系。當(dāng)你需要一個(gè) CoffeeMaker ,Dagger 會(huì)調(diào)用 new CoffeeMaker() 并且設(shè)置(set) CoffeeMaker 的可注入字段,來獲得一個(gè)實(shí)例。

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

  • 接口不能被構(gòu)造。(這個(gè)應(yīng)該都o(jì)k)
  • 第三方的類不能被注解。
  • 必須配置可配置對象。(啥意思?)

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

例如, 當(dāng)需要一個(gè) Heater 對象時(shí),那么就會(huì)調(diào)用 provideHeater()

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

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

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

所有的 @Provides 方法必須屬于一個(gè)module,這個(gè)module就是一個(gè)被 @Module 注解標(biāo)注的類。

/**
 * 一個(gè)普通的類,被 @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 為后綴。

構(gòu)建圖(一直在說對象圖,終于能看到到底是啥了,是數(shù)據(jù)結(jié)構(gòu)中的圖嗎?)

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

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

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

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

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

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

會(huì)生成一個(gè)叫做 DaggerFoo_Bar_BazComponentcomponent 組件。

任何具有可訪問的默認(rèn)構(gòu)造方法的 module 組件都可以被省略,因?yàn)槿绻麤]有設(shè)置(不將 module 對象設(shè)置到 builder里),builder (建造器)將自動(dòng)構(gòu)建實(shí)例。如果 module 模塊中,被 @Provides 注解標(biāo)注的方法都是靜態(tài)的,那么 @Component 注解標(biāo)注的 component 組件(是一個(gè)接口)的實(shí)現(xiàn)不需要?jiǎng)?chuàng)建實(shí)例。如果所有的依賴關(guān)系,都可以在不創(chuàng)建依賴實(shí)例的情況下構(gòu)建,那么(Dagger)生成的實(shí)現(xiàn)( component 接口的實(shí)現(xiàn))也會(huì)有一個(gè) create() 方法,通過該方法可以獲得一個(gè)新的實(shí)例,而略過 builder (不需要使用 builder)。

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

CoffeeShopCoffeeApp 類的一個(gè)內(nèi)部接口,被 @Component 注解標(biāo)注。這樣,CoffeeApp類,就可以簡單的使用Dagger為我們生成的 CoffeeShop 的實(shí)現(xiàn)類對象來獲得完全注入的 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()后會(huì)得到CoffeeMaker對象,然后調(diào)用CoffeeMaker對象的brew()方法
        coffeeShop.maker().brew();
    }
}

現(xiàn)在已經(jīng)構(gòu)建好了對象圖,也將切入點(diǎn)注入(就是注入了依賴),運(yùn)行程序如下所示:


image

關(guān)于圖中的綁定

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

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

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

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

/**
 * 如果下載了上述的demo項(xiàng)目,可以嘗試把 @Singleton去掉后運(yùn)行程序
 * 會(huì)發(fā)現(xiàn)輸出日志中會(huì)少一行:=> => pumping => =>
 * 是因?yàn)樵谳敵龅臅r(shí)候做了一個(gè)判斷,判斷 heater.isHot() 是否為true
 * 沒輸出說明不是true
 * 但是會(huì)發(fā)現(xiàn)輸出了:~ ~ ~ heating ~ ~ ~,會(huì)發(fā)現(xiàn)存在:this.heating = true
 * 明明設(shè)置為true了,判斷的時(shí)候?yàn)槭裁床煌ㄟ^?
 * 可以嘗試在 `Thermosiphon.pump()` 中輸出 heater
 * 在 `ElectricHeater.on()` 中輸出 this
 * 會(huì)發(fā)現(xiàn)是兩個(gè)對象
 * 在使用Dagger2時(shí)要尤其注意Scope
 */
@Provides 
@Singleton 
static Heater provideHeater() {
  return new ElectricHeater();
}

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

@Singleton
class CoffeeMaker {
  ...
}

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

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

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

Reusable scope (可重用的范圍)

有時(shí)候你想限制被 @Inject 注解標(biāo)注的類的實(shí)例化次數(shù),或者被 @Provides 注解標(biāo)注的方法的調(diào)用次數(shù),但是你不需要保證在任何特定 component 組件 或 subcomponent 子組件的生命周期中使用完全相同的實(shí)例對象。這在Android環(huán)境中很有用,因?yàn)榉峙涞某杀究赡芎芨?這個(gè)成本指的是?)。

對于這些綁定來說,你可以使用 @Reusable 域。 @Reusable 域注解,與其他的域有一些不同,它不與任何單例的 component 組件產(chǎn)生聯(lián)系。相反,實(shí)際使用綁定的 component 組件會(huì)緩存被返回的或?qū)嵗膶ο螅]懂)。

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

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

//我們使用多少范圍注解并不重要,但是別浪費(fèi)他們。
@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
  @Inject CoffeeScooper() {}
}

@Module
class CashRegisterModule {
  //別這么做,你得關(guān)心你到底把你的現(xiàn)金放到了哪個(gè)注冊表中。(關(guān)心把對象放到了哪個(gè)范圍中)
  //使用一個(gè)特殊的域替換。
  @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();
  }
}

//別這么做,你每次都想創(chuàng)建一個(gè)新的過濾器,所以你不需要指定范圍
@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 (可釋放的引用)

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

當(dāng)綁定的時(shí)候使用了域注解,那就意味著這個(gè) component 組件對象持有一個(gè)綁定對象的引用,直到這個(gè) component 對象被垃圾回收機(jī)制回收。在安卓敏感的內(nèi)存環(huán)境中,當(dāng)內(nèi)存不足時(shí),你也許想讓沒有正在使用的域?qū)ο蟊焕厥諜C(jī)制處理。

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

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

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

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

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

如果你確定內(nèi)存不足情景已經(jīng)過去(就是又有內(nèi)存了),那么你可以為任意已緩存的在低內(nèi)存時(shí)期調(diào)用了 releaseStrongReferences() 后還沒有被垃圾回收機(jī)制回收的對象恢復(fù)成強(qiáng)引用。(這個(gè)定語是真的長。。。。)

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

Lazy injections (懶注入)

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

有時(shí)你需要返回得到多個(gè)實(shí)例,而不是僅僅注入一個(gè)單例的對象。當(dāng)你有幾個(gè)選項(xiàng)(工廠,建造者等)時(shí),一個(gè)選項(xiàng)要注入一個(gè) Provider<T> 而不是只注入一個(gè) T。每次調(diào)用 Provider<T>get() 方法時(shí),Provider 都會(huì)調(diào)用 T 的綁定邏輯。如果這個(gè)綁定邏輯是一個(gè)被 @Inject 標(biāo)注的構(gòu)造方法,那么再次調(diào)用 T 的綁定邏輯就意味著再次調(diào)用 T 的構(gòu)造方法,這時(shí),就創(chuàng)建了一個(gè) T 的新對象,但是如果綁定規(guī)則是一個(gè)被 @Provides 標(biāo)注的方法,那么就不能夠保證會(huì)再次新建一個(gè)對象(方法的返回值是我們?nèi)我鈱懙模绻覀儧]寫類似 new T() 的代碼,那么自然就不會(huì)創(chuàng)建新的 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();
      ...
    }
  }
}

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

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

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

Qualifiers (限定)

有時(shí)單獨(dú)的類型不足以識(shí)別依賴關(guān)系。例如,精致的咖啡機(jī)在加熱不同的物品時(shí)應(yīng)該使用不同的加熱器。

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

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

你可以創(chuàng)建你自己的限定注解,或者直接使用 @Named 。使用限定注解標(biāo)注在感興趣的屬性和參數(shù)上。類型和限定注解,都會(huì)被用來表示依賴項(xiàng)。

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

通過標(biāo)注一致的被 @Provides 方法,提供限定的值。

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

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

依賴關(guān)系可能不會(huì)具有多個(gè)限定注解。(對于一個(gè)依賴項(xiàng)而已,基本上一個(gè)限定注解標(biāo)注就夠了)

Optional bindings (可選綁定)

如果你想要 component 中的某些依賴項(xiàng)沒有被綁定的情況下,依然能夠正常運(yùn)行,那么可以在 module 中添加一個(gè) @BindsOptionalOf 標(biāo)注的抽象方法:

@BindsOptionalOf 
abstract CoffeeCozy optionalCozy();

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

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

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

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

和下面的一個(gè)道理

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

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

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

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

Binding Instances (綁定實(shí)例)

通常在構(gòu)建 component 時(shí),你會(huì)得到可用的數(shù)據(jù)。例如,假設(shè)你有一個(gè)使用命令行參數(shù)的應(yīng)用程序,你也許想在你的 component 組件中綁定那些參數(shù)。

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

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

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

然后你的應(yīng)用就會(huì)像這樣:

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

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

如果一個(gè)被 @BindsInstance 標(biāo)注的方法中的參數(shù)被 @Nullable 標(biāo)注,那么這個(gè)綁定就會(huì)被認(rèn)為是可空的,就像 @Provides 標(biāo)注的方法也是可空的一樣,注入點(diǎn)也一定標(biāo)記了 @Nullable,那么 null 就是該幫綁定的一個(gè)可接收的值。此外, Builder 的用戶可以不調(diào)用這個(gè)方法,那么 component 組件會(huì)認(rèn)為這個(gè)實(shí)例是一個(gè)null值。

@BindsInstance 標(biāo)注的方法應(yīng)該優(yōu)先使用構(gòu)造函數(shù)參數(shù)編寫 @Module 并且立即提供這些值。

Compile-time Validation (編譯期驗(yàn)證)

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

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

當(dāng)編譯上述代碼, javac 會(huì)拒絕缺少的綁定:

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

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

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

Dagger 的注解處理器也會(huì)生成名字類似于 CoffeeMaker_Factory.java 或者 CoffeeMaker_MembersInjector.java 這樣的資源文件。這些文件是Dagger實(shí)現(xiàn)的細(xì)節(jié)。你不應(yīng)該直接使用它們,盡管在進(jìn)行 debug 調(diào)試它們時(shí)會(huì)很方便。你唯一應(yīng)該在代碼里引用的是那些根據(jù)你的 component 生成的前綴為 Dagger 的類型。

Using Dagger In Your Build

你需要在你的應(yīng)用程序運(yùn)行時(shí)包含 dagger-2.X.jar 。為了觸發(fā)代碼生成器你需要在編譯期構(gòu)建時(shí)包含 dagger-compiler-2.X.jar 。查看更多信息 讀我

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容