Dagger2略深入淺出的梳理

****(說在最前:閱讀本篇之前,希望大家對Dagger2已經有了一個初步的了解。從而幫助感覺似是而非的同學進一步的理清一些問題)****

 談到Dagger2 第一印象就是難于上手,事實上也的確如此。關于Dagger2,之前,需要一些很多知識作為鋪墊。

從最基本的概念開始:
依賴注入是怎么一回事兒;注解又是怎么一回事兒;
(其中對注解的運用又涉及到源碼級框架
關于Java注解
注解處理器
注解處理器能夠在編譯源碼期間掃描Java代碼中的注解,并且根據相關注解動態生成相關的文件。之后在程序運行時就可以使用這些動態生成的代碼。值得注意的是,注解處理器運行在跟最終程序不同的虛擬機,也就是說,編譯器為注解處理器開啟了另外一臺虛擬機來運行注解處理器。
所以我們看到的那些@,是在幫助我們在編譯期間輔助創建一些依賴注入的代碼:性能上Dagger2是優于Spring,但帶來的是編譯階段時間的延長。這樣的話,每當我們修改或者添加這些注解代碼的時候,就需要我們重新Build一下(即由apt插件來生成我們所需要使用的代碼)。對此也帶來一問題,我們需要正確的配置以及寫出正確的Dagger2代碼,這也是為什么說上手難的原因之一。

舉個例子:
一個容器里面裝的是蘋果,不用Dagger2的情況下我們可以這么寫:

    Fruit f = new Apple(color,size);
}```
上面的代碼有個問題,Container依賴了Apple實現,如果某天需要修改Apple為Banana,那么我們一定得改Container里面的代碼。有沒有一種方法可以不改Container呢? 

那便是我們所提到的**依賴注入----Dagger2。
通常依賴注入有很多類別,例如setter注入;構造函數注入;依賴獲?。环瓷湟蕾嚨鹊?,這里不展開討論。Dagger2就是通過編譯期間輔助創建代碼來幫助我們進行依賴獲取的依賴注入。**

在這里,我們就可以使用Dagger2,我們可以把代碼改成
```public class Container{ 
    @Inject
      Fruit f;
     ...
}```

這樣,Container的成員變量就自動初始化成Apple實例了,Container不用關心具體用哪個Fruit的實現。假如某一天要把Apple替換Banana,Container的代碼是完全不需要改動的。從某種意義上說,Dagger2就是一個幫你寫工廠代碼的工具。當然Dagger2的功能比工廠模式更加強大。

接下來,要再弄懂一個關鍵詞就是清楚的大前提便是**JSR-330**!
簡而言之就是:
Java的依賴注入為注入類定義了一組標準注解(和一個接口),進而最大限度地提高java代碼的可重用性,可測試性和可維護性。

-----------------------------------更深層理論內容的分割線-------------------------------------------

為了更好的弄懂Dagger2,就得弄懂依賴注入的基本原理和下面每個玩意兒的概念,這很重要?。ㄒ彩菫槭裁撮_頭入手難的原因之二)
- @Inject: 總的來說,用這個注釋表示我們要請求依賴了。 換句話說,你使用它告訴Dagger注釋的類或字段想要參與依賴注入。從而,Dagger將構造這個注釋類的實例并滿足它們的依賴性。

- @Module: Modules是其方法提供依賴性的類,因此我們定義一個類并使用@Module注釋它,因此,Dagger將知道在哪里找到依賴,以便在構造類實例時滿足它們。**Modules的一個重要特性是它們被設計為可以被分割以及組合在一起使用(例如,過會兒我們將看到,在下面的代碼中,可以有多個組合Modules)。**

- @Provide: 在Modules內部,我們定義了包含這個注釋的方法,告訴Dagger我們如何構造和提供那些提到的依賴。

- @Component: Components總的來說就是注入器,稱為@Inject 和@Module之間的橋梁,它的主要職責是將這兩部分融合到一起。**Components是你所定義的所有類型的實例(如果理解不了換個想法Component 用于連接module 作為接口 暴露需要操作的方法 將依賴對象自動注入到Container中
)**。例如,我們必須使用@Component注釋一個接口,并列出將組成該組件的所有@Modules,如果它們中的任何一個丟失了,我們在編譯時會遇到報錯。 所有Components都得知道它通過其Modules提供的依賴的范圍。

- @Scope:  Scopes是非常有用的**(也是很有迷惑性的,最下面有梳理)**,Dagger2有一種更具體的方式——自定義注釋來做范圍。隨后下面會有例子可以從中看到,這是一個非常強大的功能,就如前面指出的,沒有必要讓每個對象都知道如何管理自己的實例。Scope示例是具有自定義@PerActivity注釋的類,因此只要我們的Activity活著,此對象就會存在。換句話說,我們可以定義范圍的程度(@PerFragment,@PerUser等)。

- @Qualifier: **當類的類型不足以識別依賴性時,我們使用此注釋。** 例如,在Android的情況下,我們需要不同類型的context,所以我們可以定義一個限定符(qualifier)注釋“@ForApplication”和“@ForActivity”,因此當注入context時,我們可以使用這些限定符來告訴Dagger哪種類型是我們想要提供的context。

----------------------------------------------實踐為王-----------------------------------------------------
**結構**

Dagger2要實現一個完整的依賴注入,必不可少的元素有三種,Module,Component,Container。 
![image.png](http://upload-images.jianshu.io/upload_images/95044-1225a2c9e4f1d356.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

Container就是可以被注入的容器,具體對應上文例子中的Container,Container擁有需要被初始化的元素。需要被初始化的元素必須標上@Inject,只有被標上@Inject的元素才會被自動初始化。@Inject在Dagger2中一般標記構造方法與成員變量。
Module 可以說就是依賴的原材料的制造工廠,所有需要被注入的元素的實現都是從Module生產的。
有了可以被注入的容器Container,也有了提供依賴對象的Module。我們必須將依賴對象注入到容器中,這個過程由Component來執行。Component將Module中產生的依賴對象自動注入到Container中。


**配置**

// Add Dagger dependencies

dependencies {
compile 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

如果你使用的Android gradle plugin版本低于2.2參考 [https://bitbucket.org/hvisser/android-apt](https://bitbucket.org/hvisser/android-apt).或者試試如下配置

project的build.gradle添加

dependencies {
... // 其他classpath
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //添加apt命令
}


module的build.gradle添加

// 添加其他插件
apply plugin: 'com.neenbedankt.android-apt'//添加apt命令

dependencies {
apt 'com.google.dagger:dagger-compiler:2.0.2' //指定注解處理器
compile 'com.google.dagger:dagger:2.0.2' //dagger公用api
provided 'org.glassfish:javax.annotation:10.0-b28' //添加android缺失的部分javax注解
}

**實現**

實現Module 
Module其實就是一個依賴的制造工廠。我們只需要為其添加制造依賴的方法即可,繼續上文實現蘋果容器的例子。

@Module //1 注明本類屬于Module
public class FruitModule{
@Provides //2 注明該方法是用來提供依賴對象的特殊方法
public Fruit provideFruit(){
return new Apple(Color.RED,Size.BIG);
}
}

(1)中添加注解@Module注明本類屬于Module 
(2)中添加注解@Provides注明該方法是用來提供依賴對象的特殊方法 
一個完整的Module必須擁有@Module與@Provides注解 

實現Component 
Component就是一個將Module生成的實例注入Container中的注入器。我們來寫一個水果注入器:

@Component(modules={FruitModule.class}) //3 指明Component在哪些Module中查找依賴
public interface FruitComponent{ //4 接口,自動生成實現
void inject(Container container); //5 注入方法,在Container中調用
}

(3)中@Component使用modules指向使用的Module的集合。 
(4)所有的Component都必須以接口形式定義。Dagger2框架將自動生成Component的實現類,對應的類名是Dagger×××××,這個例子中對應的實現類是DaggerFruitComponent 
(5)中添加注入方法,一般使用inject做為方法名,方法參數為對應的Container

實現Container 
Container就是可以被注入依賴關系的容器。具體實現如下

public Container{
@Inject //6 添加@Inject,標記f可以被注入
Fruit f;
public void init(){
DaggerFruitComponent.create().inject(this); //7 使用FruitComponent的實現類注入
}
}

Container除了代碼中(6)標記f需要被注入外,還需要代碼中(7)調用Component的實現類將Module的生成的對象注入到f中。

到此,當調用Container的init()方法時,Contianer中的f將會自動初始化成實現類Apple的對象。 
以后如果想更改Fruit的實現類,只需要在@Component中的modules指向不同的Module即可。而Container的代碼完全不需要改動。因為Container已經不再依賴Apple實現了。

---------------------------------------------------巨大的例子---------------------------------------------
如此簡單就肯定不值得我們說了。。所以。。下面
我們用Google官方的android-architecture-todo-mvp-dagger項目來深入實踐探討。
首先從Application類入手

public class ToDoApplication extends Application {

private TasksRepositoryComponent mRepositoryComponent;

@Override
public void onCreate() {
    super.onCreate();

    mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
            .applicationModule(new ApplicationModule((getApplicationContext())))
            .tasksRepositoryModule(new TasksRepositoryModule())
            .build();

}

public TasksRepositoryComponent getTasksRepositoryComponent() {
    return mRepositoryComponent;
}

}

DaggerTasksRepositoryComponent就是由Dagger2幫我們生成的代碼。
對比上面的代碼用DaggerXXXXComponent.create()實際上等價于DaggerXXXXComponent.builder().build()。
在構建的過程中,默認使用Module無參構造器產生實例。如果需要傳入特定的Module實例,可以像上面一樣用。Module只有有參構造器,顯式傳入Module實例。

1、為什么要寫這樣的application
Dagger編寫Android應用程序的困難中心點是許多Android框架類都是由操作系統本身實例化的,像 Activity和Fragment,只有讓Dagger可以創建所有注入的對象才能完美的運作。為此必須在生命周期方法中執行成員注入。

Component中包含了TasksRepositoryModule, ApplicationModule兩個module.我們可以詳細的看一下

/**

  • This is used by Dagger to inject the required arguments into the {@link TasksRepository}.
    */
    @Module
    public class TasksRepositoryModule {

    @Singleton
    @Provides
    @Local
    TasksDataSource provideTasksLocalDataSource(Context context) {
    return new TasksLocalDataSource(context);
    }

    @Singleton
    @Provides
    @Remote
    TasksDataSource provideTasksRemoteDataSource() {
    return new FakeTasksRemoteDataSource();
    }

}

這個里面用到@Singleton@Provides@Local@Remote
@Singleton:是用來限制創建單實例對象的注解
@Provides:注明該方法是用來提供依賴對象的特殊方法
@Local@Remote是自定義的注解用@Qualifier元注解實現

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Local {

}


@Module
public final class ApplicationModule {

private final Context mContext;

ApplicationModule(Context context) {
    mContext = context;
}

@Provides
Context provideContext() {
    return mContext;
}

}

在ApplicationModule中很明顯的一點,并沒有直接返回new xxxx這樣的實例。而是在@Provides下用了一個有返回值的方法。這就是要說到的
***如果Module只有有參構造器,則必須顯式傳入Module實例。***

這里創建好了之后,我們就能在activity、fragment里拿到component并且inject()注入

TasksActivity.java
DaggerTasksComponent.builder()
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
.inject(this);

---------------------------------------------解析

2、依次往下是神馬結構以及完整的一套流程:
在TasksActivity.java進行了控件的初始化,fragment和presenter的創建。
可以看到在變量聲明處,聲明了 @Inject TasksPresenter mTasksPresenter;
然后通過上面的代碼段,在onCreate周期里進行了實例化的創建。

然后具體再看,順著inject(this),我們進入到TasksComponent。

@FragmentScoped
@Component(dependencies = TasksRepositoryComponent.class, modules = TasksPresenterModule.class)
public interface TasksComponent {

void inject(TasksActivity activity);

}

我們知道,Module里是提供依賴對象的特殊方法,在此指明的
modules = TasksPresenterModule我們進入可以看到如下關鍵方法:

@Provides
TasksContract.View provideTasksContractView() {
return mView;
}

然而在TasksPresenterModule里
并沒有顯示的提供實例,所以我們需要去TaskPresenter里的構造函數里去看。
為什么呢?
我們需要知道的是,Dagger給我們一些注入依賴的選項:
**構造函數注入:通過用@Inject注釋我們類的構造函數。
字段注入:通過用@Inject注釋我們類的(非私有)字段。
方法注入:通過使用@Inject注釋一個方法。**

這也是Dagger在綁定依賴項時使用的順序,它很重要,因為它可能在你有一些奇怪的行為后發生NullPointerExceptions錯誤,或者更甚,這意味著你的依賴可能沒有在對象創建的時刻初始化。 這在Android上在Activities或Fragments中使用字段注入時很常見,因為我們沒有訪問它們的構造函數。

所以,我們再依照如此的順序,可以在TasksPresenter里看到它的構造函數是添加了@Inject注釋的,也就是說,我們是通過這里來提供實例化對象的。也就是上面代碼段所return的TaskContract.View類型的mView。

與此之外構造函數里還需要另外一個參數TasksRepository tasksRepository,在哪里能得到這個參數呢,往回倒倒,我們發現在TasksComponent里@Component注釋處,**除了通常的module之外,還有一個dependencies,這個就是說我們的這個Component還依賴著另一個TasksRepositoryComponent。這個Component就會必須定義帶返回值的方法來提供我們所缺少的依賴 。**

好了,到這里我們就知道@Inject TasksPresenter mTasksPresenter是怎樣得到實例化的了。然而并沒有完,我們接著進入到的TasksPresenter里繼續看。

發現,除了構造函數之外,緊挨著下面有一個方法也被標注了@Inject注釋

/***
* Method injection is used here to safely reference {@code this} after the object is created.
* For more information, see Java Concurrency in Practice.
*/
@Inject
void setupListeners() {
mTasksView.setPresenter(this);
}```
這就是上面提到的第二種方式——字段注入:通過用@Inject注釋我們類的(非私有)字段。然后這個方法會在什么時候調用呢。我們可以通過在build構建了Dagger2的代碼之后,觀察到在

public final class TasksPresenter_MembersInjector implements MembersInjector<TasksPresenter> {
  public TasksPresenter_MembersInjector() {}

  public static MembersInjector<TasksPresenter> create() {
    return new TasksPresenter_MembersInjector();
  }

  **@Override
  public void injectMembers(TasksPresenter instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.setupListeners();
  }

有injectMembers這么一個方法injectMembers()里調用了我們@Inject注釋的setupListeners()方法。

關于injectMemers()方法在官方文檔里是這樣說到的:
injectMembers
void injectMembers(T instance)
Injects dependencies into the fields and methods of instance. Ignores the presence or absence of an injectable constructor.
Whenever the object graph creates an instance, it performs this injection automatically (after first performing constructor injection), so if you're able to let the object graph create all your objects for you, you'll never need to use this method.

Parameters:
instance - into which members are to be injected
Throws:
NullPointerException - if instance is null

里面有說到它會自動執行這個注入(首次執行構造函數注入后)

再繼續往上追尋,可以發現,是在DaggerTasksComponent里的inject方法里有調用(具體代碼請自行查閱),這個我們Activity里用到的Dagger2框架將自動生成Component的實現類DaggerTasksComponent。也就是說setupListeners()是在調用了.inject(this)之后。跟著我們需要的字段被實例化后,跟著被調用的。

眼尖的同學肯定還注意到了,除了我們分析的這個方法調用點的問題之外,上面還有幾行官方的注釋,大概是說在方法注入用在這里是創建了安全引用后(this),然后更多信息參考java并發實踐。如何并發安全,final類型。創建后狀態不能被修改的對象叫做不可變對象。不可變對象永遠是線程安全的。

所以~~我們可以看到,TasksPresenter這個類,是被final修飾了的。
流程走到這里,我們就已經清晰的看到了依賴注入給我們創建的實例,以及相關的一些用法。剩下的關于MVP架構的交互方式就不在這里展開了。

3、更多的可能方式和方法
實現單例、Subcomponent、Lazy、Provider、Multibindings等等
未完待續……
---------------------------------------------------接著說-------------------------------------------------

4、需要啰嗦的部分:
關于scope

很多初學者,誤以為添加上自定義的Scope后,MainComponent就會獲得一種神奇的能力,能夠自動實現與MainActivity的“生死與共”。這種想法天真爛漫可愛,但卻是錯誤的。

添加上自定義的Scope后,MainComponent并沒有獲得什么超能力,要想讓它與MainActivity的生命周期同步,還需要我們手動完成,即:

MainActivity的onCreate方法中創建MainComponent并完成依賴注入
MainActivity的onDestroy方法中銷毀MainComponent
------------------------------------------------暫時收工-------------------------------------------------

參考以及引用資料:
1、依賴注入相關:
Android 中的依賴注入
依賴注入那些事兒
Java 依賴注入標準(JSR-330)

2、注解相關:
Java注解
源碼級框架(注解處理器)

3、工廠模式:

4、Dagger2相關:
Android常用開源工具(1)-Dagger2入門
解析Dagger中的Scope
都是套路——Dagger2沒有想象的那么難
聊聊 Android 中的依賴注入
Google官方MVP+Dagger2架構詳解【從零開始搭建android框架系列(6)】
Dagger2深入理解
Tasting Dagger 2 on Android
Dependency injection with Dagger 2 - Custom scopes
谷歌官方Dagger2文檔

5、demo:
Android Architecture Blueprints [beta] - MVP + Dagger2
GithubClient

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容