PS: 本篇文章的示例代碼,放在了項目的chapter3分支
上一篇文章我們介紹了使用@inject聲明提供類型,并據(jù)此實(shí)現(xiàn)了Car和Engine的完全依賴注入,這篇文章我們使用Module來實(shí)現(xiàn)同樣的效果。
我們先梳理一下上篇文章里的依賴關(guān)系,我們最后總結(jié)了它的依賴模型是這樣的:
D(Car,Engine) = D(Car,DaggerCarComponent)
加上Dagger2托管的依賴關(guān)系實(shí)際上是這樣的:
D(Car,Engine) = D(Car,DaggerCarComponent) + D(DaggerCarComponent,Car構(gòu)造方法) + D(Car構(gòu)造方法, Engine構(gòu)造方法)
我們先調(diào)整Engine的提供,使用Module來實(shí)現(xiàn)。
定義EngineModule
首先我們聊一下什么是Module,Module是Dagger2為我們定義的一個角色,它是一個“提供類型P(?)”的容器,我們用@Module注解一個類來聲明這個類是一個module,在這個類里面,所有用@Provides注解的返回類型不為空的方法都是合法的提供類型。要注意的是,Dagger2框架不為Null對象做任何的錯誤兼容處理,相反地,它會在注入前檢查Module創(chuàng)建的對象是否為空,如果為空就報空指針錯誤,所以我們要確保Module的提供方法返回一個可用的對象。
如果我們想返回可能為空的對象,可以使用@Nullable注解聲明,這個注解是類型遞歸的,使用了這個注解的方法,Component里也必須同樣聲明,否則會編譯失敗。
現(xiàn)在我們創(chuàng)建一個EngineModule來實(shí)現(xiàn)P(Engine)。
@Module
public class EngineModule {
@Provides
Engine provideEngine(){
return new Engine(2);
}
}
聲明對EngineModule的依賴
創(chuàng)建后我們要告訴CarComponent使用EngineModule來作為它的提供類型的容器,查找它需要的提供類型。Component注解定義了modules索引的class數(shù)組參數(shù),用來聲明它依賴的所有Module。
這里和使用Inject注解構(gòu)造方法不同之處在于,類的構(gòu)造方法總是唯一的(參數(shù)類型唯一),因此它作為“提供類型”也是唯一的,所以Dagger2會自動將構(gòu)造方法聲明的提供類型,作為所有Component的備用類型,不用我們手動告訴具體的Component。但是如果在Module里聲明提供類型,我們可以在多個Module里聲明相同的提供類型,根據(jù)不同的使用需求對Component進(jìn)行裝配,因此多了告訴Component它的Modules依賴這一步,這一步也是我們使用Dagger2來管理依賴關(guān)系的優(yōu)越性的體現(xiàn):我們只要修改注解,就可以修改具體的依賴關(guān)系。
我們修改一下CarComponent接口:
@Component(modules = EngineModule.class)
public interface CarComponent {
void inject(Car car);
Car getCar();
}
現(xiàn)在我們使用Module聲明了Engine的提供類型,main方法的調(diào)用方式保持不變:
public class Main {
public static void main(String[] args){
Car car = DaggerCarComponent.builder().build().getCar();
System.out.println("cylinderNumbers : " + car.getEngine().getCylinderNumbers());
}
}
我們運(yùn)行一下:
cylinderNumbers : 2
Process finished with exit code 0
Engine的氣缸數(shù)目已經(jīng)變?yōu)榱?,也就是說我們修改生效了。
Module的工作機(jī)制
現(xiàn)在我們再看看生成的注入代碼,了解一下Module的工作機(jī)制。我們先看下getCar方法:
@Override
public Car getCar() {
return injectCar(
Car_Factory.newCar(EngineModule_ProvideEngineFactory.proxyProvideEngine(engineModule)));
}
對比使用@Inject聲明的模板代碼,我們發(fā)現(xiàn)前半部分
injectCar(Car_Factory.newCar(xxx));
是一樣的,不同的是前者直接創(chuàng)建了一個Engine對象,而這里使用了模板來獲取對象,Dagger為我們生成了與Module名稱和方法名稱對應(yīng)關(guān)系為:
模塊名_方法名Factory.proxy方法名(模塊名 instance)
的靜態(tài)類和方法,接受EngineModule對象,并作為它的代理來獲取對象,我們看看代理方法:
public static Engine proxyProvideEngine(EngineModule instance) {
return Preconditions.checkNotNull(
instance.provideEngine(), "Cannot return null from a non-@Nullable @Provides method");
}
這里調(diào)用了Module實(shí)例中我們自己定義的provideEngine方法,并校驗(yàn)module生成的對象是否為空。我們再看看這個EngineModule實(shí)例是怎么初始化的:
public CarComponent build() {
if (engineModule == null) {
this.engineModule = new EngineModule();
}
return new DaggerCarComponent(this);
}
在使用Builder構(gòu)建CarComponent對象的時候,如果為空就創(chuàng)建一個新的實(shí)例,并傳遞給Component對象。同時我們也看到,在Builder中生成了指定module的方法:
public Builder engineModule(EngineModule engineModule) {
this.engineModule = Preconditions.checkNotNull(engineModule);
return this;
}
所以我們可以總結(jié)一下,如果我們?yōu)锳Component實(shí)現(xiàn)XModule的YMethod方法,來聲明一個P<Type>,Dagger為我們做了以下的工作來完成這個功能。
- 生成XModule_YMethodFactory.proxyYMethod(XModule instance)的靜態(tài)代理類和方法,并作為Type實(shí)例的獲取代理
- 在AComponent和AComponent.Builder中生成XModule的成員,并為XModule成員生成建造模式的初始化方法。
- 在getType()方法里調(diào)用第1步生成的代理方法,獲取Type實(shí)例作為依賴,創(chuàng)建我們要的目標(biāo)實(shí)例。
這個環(huán)節(jié)完成的依賴模型替換為:
D(DaggerCarComponent,Engine) = D(DaggerCarComponent,EngineModule_ProvideEngineFactory) + D(EngineModule_ProvideEngineFactory, EngineModule) + D(EngineModule,Engine)
這里值得注意的有以下幾點(diǎn):
- 之所以在Builder里生成XModule成員的建造模式方法,是為了提供機(jī)制和能力,讓我們更加靈活地管理具體的Module,控制對象實(shí)例化邏輯。
- D(EngineModule,Engine) 是我們手動實(shí)現(xiàn)的,是我們真正的業(yè)務(wù)邏輯,需要我們多加關(guān)注,保證邏輯符合預(yù)期。
小結(jié)
在這一節(jié)中,我們使用module來實(shí)現(xiàn)了Engine的提供,并通過源碼和邏輯模型,分析了module的工作機(jī)制以及其背后的設(shè)計原理。但是我們看到,作為Car類的組件,依賴了Engine的Module,稍微有點(diǎn)別扭。有時候我們想控制Component、Module的對應(yīng)關(guān)系,比如為了使我們的代碼邏輯更加清晰可讀,將這種復(fù)用的需求,通過組件的復(fù)用來實(shí)現(xiàn)。下一篇文章,我們將講解如何復(fù)用Component,敬請期待。