原文地址
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_BazComponent
的 component
組件。
任何具有可訪問的默認構造方法的 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();
CoffeeShop
是 CoffeeApp
類的一個內部接口,被 @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();
}
}
現在已經構建好了對象圖,也將切入點注入(就是注入了依賴),運行程序如下所示:
關于圖中的綁定
上述的例子展示了怎么使用一些更典型的綁定( 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
用于上述任何綁定的包裝器 - 上述任何綁定的
Lazy
的Provider
(例子: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>
(AAASuper
是 AAA
的超類),這樣如果改了需求要將數據轉換到 List<BBB>
中,只需要更改注入的對象即可,不需要改動當前類的任何代碼。
筆記:注入的 Provider<T>
可能創建了難以理解的代碼,也可能是在對象圖中設計了錯誤的范圍或錯誤的對象結構。通常你想使用 factory
或者是 Lazy<T>
或者是更改你代碼的生命周期和代碼結構,使得你能夠直接注入一個 T
。 但是,在某些情況下,注入 Provider<T>
可以節省生命(啥意思?)。一個普遍的用途是當你必須使用不符合對象自然生命周期的傳統框架時(就用唄就?)。(這個括號也是原文中的內容)(例子:servlets
被設計成單例模式,但是僅僅在 request
的上下文中有效。)
(這里是我加的:) Servlet
是javaweb中處理請求的模塊,運行在java客戶端的java程序叫做 applet
,為客戶端提供支持的叫 server
,servlet
就是 server
和 applet
的組合詞。在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
。你可以一直注入其他的格式,因為 Provider
和 Lazy
的 get()
方法能夠返回 null
。
在一個 component
組件中的可選擇的隱藏的綁定,如果 subcomponent
子組件包含基礎類型的綁定,那么可以在 subcomponent
子組件中顯示。
可以使用 Guava’s Optional 或者 Java 8’s Optional。
Binding Instances (綁定實例)
通常在構建 component
時,你會得到可用的數據。例如,假設你有一個使用命令行參數的應用程序,你也許想在你的 component
組件中綁定那些參數。
也許你的應用程序只需要一個參數來表示你想要注入的用戶名( @UserName String
)。你可以在 component
的 builder
中添加一個 @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
被一個沒有綁定 Executor
的 component
實例化:
@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
。查看更多信息 讀我 。