Dagger2:介紹與使用

dagger2是用于進(jìn)行依賴注入的框架。dagger1由square開發(fā),而現(xiàn)在dagger2由google繼續(xù)開發(fā)和維護(hù)。在google出的幾個Android架構(gòu)示例(https://github.com/googlesamples/android-architecture/tree/todo-mvp-dagger, https://github.com/googlesamples/android-architecture-components/tree/master/GithubBrowserSample)中也用到了dagger2,給人一種谷歌欽定的感覺。
那么,我們?yōu)槭裁匆玫絛agger呢?它是用來干嘛的?
本文將通過介紹dagger2的相關(guān)的概念和一些常用用法來初探dagger2并能夠上手使用,一些深入的用法也會在后續(xù)的文章中介紹。

1.控制反轉(zhuǎn)和依賴注入

首先,我們就要先介紹控制反轉(zhuǎn)和依賴注入的概念了。
如果A類依賴B類的對象通常我們會怎么做呢?

public class A{
    private B b;
    public A(){
        b = new B();
    }
}

這是最通常的做法:在類A里new 一個B的對象作為成員變量,也就是A控制了B實(shí)例的生成,控制依賴。這樣做的不足在于A需要了解B的細(xì)節(jié),如果要替換B模塊還需要相應(yīng)的修改A。更嚴(yán)重的是,如果B的實(shí)例化依賴了其他類C/D/F...,則A類還需要管理C/D/F類的實(shí)例化過程,這樣模塊間的耦合度會很高,修改和替換難度都很大。

而控制反轉(zhuǎn)(IoC)是一種設(shè)計(jì)思想,即設(shè)計(jì)好的對象的依賴由容器來控制,而非之前用對象來直接控制對象依賴。程序架構(gòu)發(fā)生了“主從換位”的變化,應(yīng)用程序被動的等待IoC容器來創(chuàng)建并注入它所需要的資源。

依賴注入(Dependency Injection)是控制反轉(zhuǎn)的一種具體實(shí)現(xiàn)。組件之間依賴關(guān)系由注入器(容器)在運(yùn)行期決定,即由容器動態(tài)的將某個依賴關(guān)系注入到組件之中。依賴注入將依賴統(tǒng)一管理,使得組件間耦合度降低,更容易拓展和修改,也能更好的控制組件的生命周期。如果某組件需要修改其依賴,如果新增加的依賴存在與依賴圖中,那么只需修改該類就可以,注入器可以將新的依賴注入。

2.java依賴注入標(biāo)準(zhǔn):JSR-330

java有一套依賴注入規(guī)范——JSR-330。該規(guī)范里定義了一些依賴注入相關(guān)的注解(javax.inject包下),dagger就是基于了這個規(guī)范,也就是dagger對外使用的annotation許多都是JSR-330下的。而其他的注入框架如Spring也支持JSR-330。上文的例子中,如果用支持JSR-330的依賴注入框架的話,最后實(shí)現(xiàn)的代碼可以類似于:

public class A{
    @inject
    private B b;
}

在dagger2 中用的JSR-330標(biāo)準(zhǔn)注釋有:@Inject @Qualifier @Scope @Named等。

值得注意的是,JSR-330并沒有規(guī)范注入器,也就是說用不同的注入框架時,上述代碼可以做到基本一致,都是符合JSR-330規(guī)范的,而不同框架注入器實(shí)現(xiàn)方式不同。

3.dagger2的基本結(jié)構(gòu)

Dagger Injections Overview

(圖摘自https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2)
使用dagger2進(jìn)行依賴注入時,整個依賴關(guān)系如圖。在dagger2中Component便是注入器,它維護(hù)了依賴圖并向其他對象注入依賴。依賴圖中的對象由Module提供,或是通過構(gòu)造方法注入來獲得,一些具體細(xì)節(jié)將在稍后介紹。

4.用Dagger2向類注入依賴

通過Dagger2將依賴注入到某個類中的時候,是用@Inject注解來實(shí)現(xiàn)的。@Inject注解為JSR-330標(biāo)準(zhǔn)中的一部分。
注入方式有兩種:即@Inject可以標(biāo)記域也可以標(biāo)記構(gòu)造方法。
(1)域注入

public class MainActivity extends Activity {
   @Inject 
   SharedPreferences mSharedPreferences;

   public void onCreate(Bundle savedInstance) {
       InjectorClass.inject(this);
   } 

注意,這里需要在適當(dāng)?shù)臅r機(jī),如在組件的onCreate()中調(diào)用Component的inject()方法,此時將依賴注入到該對象中。此外,被@Inject標(biāo)記的域不可為private,因?yàn)樗枰凶⑷肫鰿omponent進(jìn)行賦值。
(2)構(gòu)造方法注入

public class ProviderHandler {

    private ContentResolver mContentResolver;

    @Inject
    ProviderHandler(@NonNull Context context) {
        mContentResolver = context.getContentResolver();
    }

注意:在構(gòu)造方法注入的時候這個類成為依賴圖表的一部分。當(dāng)其他類需要該這個類的對象時,dagger2會調(diào)用該構(gòu)造方法創(chuàng)造其實(shí)例對象,該類的對象可以被注入其他對象中,如上部分的圖所示,B就是通過構(gòu)造器注入的方式生成,Component也可以根據(jù)需要將其注入到其他類中。

這兩種注入方法中,需要的被注入的對象都從依賴圖中獲取,而不需要手工控制。

而對于Android中的一些組件,如Activity/Service/Fragment等等,由于其構(gòu)造方法不由我們控制,只能通過域注入來完成。

5.Component:

Component就是我們前文提到的注入器(容器),它定義了依賴圖,并負(fù)責(zé)向其他類中注入依賴。
和Android的許多框架一樣,對于Component部分,是有開發(fā)者以接口形式寫Component,框架通過gradle插件自動生成Component的實(shí)例。形式有些像retrofit,但retrofit是通過動態(tài)代理,通過代理對象來完成接口任務(wù)的。
在實(shí)際開發(fā)中,我們定義的Component代碼類似如下:

@Singleton
@Component(modules = {ApplicationModule.class, NumberInfoModule.class})
public interface AppComponent {

    void inject(MyService service);
    void inject(MyContentProvider p  rovider);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);
        AppComponent build();
    }
}

上面例子中,我們定義了一個接口AppComponent,該接口被@Component注釋,表明這是一個dagger2中的Component,Dagger2會在編譯階段生成其實(shí)例類:DaggerXXXComponent,以這個為例就是生成DaggerAppComponent,其實(shí)現(xiàn)了APPComponent。

我們來看這個接口的各個部分:
(1)@Singleton注解:這是一個scope的注釋,dagger2的scope機(jī)制是為單利提供了生命周期的概念。@Singleton這表明了中依賴t的生命長度和Application生命長度一致。另外,@Singleton是JSR-330標(biāo)準(zhǔn)中的一部分,另外我們還可以自定義scope。有關(guān)scope的概念會在下一篇文章中介紹。
(2) Module:在上面@Component中我們指定了Module類,Module是為依賴圖提供具體依賴的對象的,也就是我們在Module中我們告訴Dagger2框架當(dāng)我們需要某個類的對象時,我們該如何獲得。再回到最上面的圖,其中A和C都是由Module提供的。也就是除了構(gòu)造方法注入的類以外,其他的依賴需要有Module提供。后面會詳細(xì)介紹。
(3)inject方法:Component在接口中,我們定義了inject的方法。在前面介紹通過域注入時,我們在組件的onCreate()方法中調(diào)用了Component的inject方法。該方法表明了我們可以將依賴圖中的依賴注入到什么類中,該類(組件)在適當(dāng)?shù)臅r候(如onCreate())調(diào)用inject方法,完成依賴注入。inject方法由我們寫接口的時候定義,編譯階段框架實(shí)現(xiàn)該方法。
(4)Builder: 在Component接口中定義Component.Builder接口,顧名思義是在定義Component的建造者。上例中,用@BindsInstance
在定義Builder時候我們可以在允許Component初始化的時候設(shè)置一些對象,如上面例子,可以給Component設(shè)置Application對象,從而將Application對象納入到依賴圖中。
另外,如果其Module的構(gòu)造函數(shù)需要傳入?yún)?shù)的話,會自動生成Component.Builder設(shè)置該Module的方法,可以參考如下例子(也是一個添加Apllication依賴的實(shí)例):

@Module
public class AppModule {
    Application mApplication;
    public AppModule(Application application) {
        mApplication = application;
    }

    @Provides
    @Singleton
    Application providesApplication() {
        return mApplication;
    }
}
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
   void inject(MainActivity activity);
   // void inject(MyFragment fragment);
   // void inject(MyService service);
}

實(shí)例化Component時可調(diào)用:

   mNetComponent = DaggerNetComponent.builder()
                .appModule(new AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
                .build();

至此,我們可見Component維護(hù)了依賴圖,而其中的依賴來源有:構(gòu)造方法注入的對象,Component.Builder的@BindsInstance,以及Module類中提供的依賴。

6.Module

上文提到了,Module類是為依賴圖中提供依賴的。一般從構(gòu)造方法提供的依賴都有明確可調(diào)用的構(gòu)造器才能夠注入,而沒有構(gòu)造方法,例如從一些靜態(tài)方法等方式獲取的依賴就需要在Module中定義了。另外還有一種情況,如果我們想注入的是接口或者是父類,而注入的是接口的實(shí)際實(shí)現(xiàn)或其子類,也需要在Module中定義。下面通過Module中使用的一些注解來進(jìn)行解釋。

@Provides

@Provides注解允許我們在Module里定義方法,方法傳入的參數(shù)是依賴圖中已存在的依賴對象,返回將是該方法提供給依賴圖的依賴。@Provides適用于需要由靜態(tài)方法提供的依賴的情況,如:

@Module
class AppModule {
    @Singleton @Provides
    GithubService provideGithubService() {
        return new Retrofit.Builder()
                .baseUrl("https://api.github.com/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(new LiveDataCallAdapterFactory())
                .build()
                .create(GithubService.class);
    }
  ...
}

另外需要通過調(diào)用依賴圖中對象的某些方法才能獲得的依賴
比如依賴圖中有Context對象,例如我們希望在依賴圖中或者ContentResolver對象,就可以定義方法:

@Module
class AppModule {
@Provides
    ContentProvider provideUserDao(Context context) {
        return context.getContentResolver();
    }
  ...

當(dāng)然,還有其他情況下需要用到Provides注解,可以根據(jù)項(xiàng)目實(shí)際情況判斷,就不舉例了。

@Binds

@Binds和@Provides最大區(qū)別就是@Binds只能修飾抽象方法,比如說當(dāng)A1類繼承自A,而在當(dāng)前的依賴圖中可以提供A1的對象(如A1已經(jīng)可以通過構(gòu)造方法注入到Component中),而在被注入類中需要A的對象,那么就可以定義Bind的抽象方法來將A1作為A的對象注入。再以上面AppComponent為例,Component實(shí)例化中通過Builder可以獲得Application的對象,而如果依賴圖中需要context,就可以提供給他們這個Application對象:

public abstract class ApplicationModule {
    //expose Application as an injectable context
    @Binds
    abstract Context bindContext(Application application);
}

所以@Binds解決了我們面向接口編程的需求。
當(dāng)然這種情況也可以用@Provides的有實(shí)體方法(方法實(shí)體是類型的強(qiáng)轉(zhuǎn)),但@Binds明顯更加清晰。

@Qualifier

而更進(jìn)一步,如果依賴圖中有兩個子類都實(shí)現(xiàn)了某一接口,而我們在注入時在不同的場景下需要用這兩個的某個,該怎么做呢?這時候我們需要@Qualifier的注解。
下面是一個實(shí)際的例子:
我們首先要定義兩個 InfoRepository和RemoteInfoSource都是數(shù)據(jù)源,都實(shí)現(xiàn)了InfoSource接口, 分別表示本地數(shù)據(jù)源和云端的數(shù)據(jù)源(這種封裝方式在MVP/MVVM架構(gòu)中非常常見)。為區(qū)分二者,我們首先要先定義兩個注釋,@Remote表示遠(yuǎn)端數(shù)據(jù),@Repository表示本地倉庫:

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {}

注意,這兩個Annotation都需要被@Qualifier修飾,@Qualifier為JSR-330標(biāo)準(zhǔn)中的一部分。
之后,InfoRepository和RemoteInfoSource在通過構(gòu)造方法注入的時候,各自的構(gòu)造方法在除了@Inject注釋外還要加上剛剛定義的對應(yīng)的Annotation:

public class InfoRepository {
    Context mContext;

    @Repository
    @Inject
    InfoRepository(@NonNull Context context) {
        mContext = context;
    }
public class RemoteInfoSource {
    Context mContext;

    @Remote
    @Inject
    RemoteInfoSource(@NonNull Context context) {
        mContext = context;
    }

接下來在Module中定義對應(yīng)的@bind的抽象方法,對應(yīng)方法也需要加對應(yīng)Annotation:

@Module
abstract public class NumberInfoModule {

    @Binds @Repository
    abstract InfoSource provideLocalDataSource(InfoRepository dataSource);

    @Binds @Remote
    abstract InfoSource provideRemoteDataSource(RemoteInfoSource dataSource);
}

在使用時,如果我們需要Remote的InfoSource時就使用:

@Inject
@Remote
InfoSource mRemoteInfoSource;

或在構(gòu)造方法注入時:

public Class TestClass {
  @Inject
  public TestClass(@Remote InfoSource cloudSource) {
    this.mRemoteInfoSource= remoteInfoSource;
  }
}

另外,@Qualifier 定義的注解可以設(shè)置參數(shù),來標(biāo)記不同的對象,如:

@Qualifier  
@Documented  
@Retention(RetentionPolicy.RUNTIME)
public @interface Source {
    String source() default "local";
}

就可以用@Source(soucrce = "remote")和@@Source(soucrce = "repository")代替上例中的@Remote和@Repository標(biāo)簽。
另外,@Named標(biāo)簽是JSR-330自帶的一個@Qualifier實(shí)現(xiàn),我們可以直接用@Named來起到和自定義@Qualifie注解r相同的效果,對應(yīng)上面例子,@Remote和@Repository的位置替換成@Named("Remote")和@Named("Repository"),通過參數(shù)來區(qū)分同樣類型的不同對象。
而需要本地倉庫時,對應(yīng)注解換成@Repository。
注意,在@Provides注解的方法中,同樣可以用@Qualifier的標(biāo)簽。

小結(jié)

這篇文章首先介紹了何為控制反轉(zhuǎn)和依賴注入,并介紹java依賴注入標(biāo)準(zhǔn):JSR-330。隨后,本文介紹了通過Dagger2構(gòu)建依賴圖的具體結(jié)構(gòu),著重介紹了如何將依賴注入到各個模塊中,將對象增加到依賴圖中的方法。介紹了Component的作用以及接口的定義方式。最后介紹了Module同@Provides和@Binds向依賴圖提供依賴,并利用@Qualifier限定注入的對象。

我們可以總結(jié)出有三種方式想依賴圖提供依賴:通過Component.Builder()的@BindInstance方式,通過構(gòu)造方法注入的對象以及有Module的@Provides和@Binds提供的依賴。

Dagger2在Android應(yīng)用中,最簡單的情況是在Application完成Component的初始化,并Application中用靜態(tài)方法向外提供Component實(shí)例,以讓其他組件通過Component完成依賴注入。
但更復(fù)雜的情況下,還需要不同生命周期的Component來控制不同依賴圖的生命,這就需要用到Scope以及Component間的依賴,以及Subcomponents,這些也都是Dagger2的重要概念,這將在下篇文章進(jìn)行介紹。

參考:
https://google.github.io/dagger/users-guide
https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2
http://www.itdecent.cn/p/8bb4651f0fbf
https://google.github.io/dagger/api/2.11/dagger/Binds.html

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

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

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