從源碼角度分析註解在Dagger2中的使用

前言

Dagger2起源於Dagger,是一款基於Java註解來實現的徹底在編譯階段完成依賴注入的開源庫,主要用於模塊間解耦、提升代碼的健壯性和可維護性。Dagger2在編譯階段經過apt利用Java註解自動生成Java代碼,而後結合手寫的代碼來自動幫咱們完成依賴注入的工做。java

起初Square公司受到Guice的啓發而開發了Dagger,可是Dagger這種半靜態半運行時的框架仍是有些性能問題(雖然說依賴注入是徹底靜態的,可是其有向無環圖(Directed Acyclic Graph)仍是基於反射來生成的,這不管在大型的服務端應用仍是在Android應用上都不是最優方案)。所以Google工程師Fork了Dagger項目,對它進行了改造。因而變演變出了今天咱們要討論的Dagger2,因此說Dagger2其實就是高配版的Dagger。框架

Dagger2註解

Dagger2是基於Java註解來實現依賴注入的,那麼在正式使用以前咱們須要先了解下Dagger2中的註解。Dagger2使用過程當中咱們一般接觸到的註解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。ide

  • @Inject:@Inject有兩個做用,一是用來標記須要依賴的變量,以此告訴Dagger2爲它提供依賴;二是用來標記構造函數,Dagger2經過@Inject註解能夠在須要這個類實例的時候來找到這個構造函數並把相關實例構造出來,以此來爲被@Inject標記了的變量提供依賴;
  • @Module:@Module用於標註提供依賴的類。你可能會有點困惑,上面不是提到用@Inject標記構造函數就能夠提供依賴了麼,爲何還須要@Module?不少時候咱們須要提供依賴的構造函數是第三方庫的,咱們無法給它加上@Inject註解,又好比說提供以來的構造函數是帶參數的,若是咱們之所簡單的使用@Inject標記它,那麼他的參數又怎麼來呢?@Module正是幫咱們解決這些問題的。
  • @Provides:@Provides用於標註Module所標註的類中的方法,該方法在須要提供依賴時被調用,從而把預先提供好的對象當作依賴給標註了@Inject的變量賦值;
  • @Component:@Component用於標註接口,是依賴需求方和依賴提供方之間的橋樑。被Component標註的接口在編譯時會生成該接口的實現類(若是@Component標註的接口爲CarComponent,則編譯期生成的實現類爲DaggerCarComponent),咱們經過調用這個實現類的方法完成注入;
  • @Qulifier:@Qulifier用於自定義註解,也就是說@Qulifier就如同Java提供的幾種基本元註解同樣用來標記註解類。咱們在使用@Module來標註提供依賴的方法時,方法名咱們是能夠隨便定義的(雖然咱們定義方法名通常以provide開頭,但這並非強制的,只是爲了增長可讀性而已)。那麼Dagger2怎麼知道這個方法是爲誰提供依賴呢?答案就是返回值的類型,Dagger2根據返回值的類型來決定爲哪一個被@Inject標記了的變量賦值。可是問題來了,一旦有多個同樣的返回類型Dagger2就懵逼了。@Qulifier的存在正式爲了解決這個問題,咱們使用@Qulifier來定義本身的註解,而後經過自定義的註解去標註提供依賴的方法和依賴需求方(也就是被@Inject標註的變量),這樣Dagger2就知道爲誰提供依賴了。----一個更爲精簡的定義:當類型不足以鑑別一個依賴的時候,咱們就可使用這個註解標示;
  • @Scope:@Scope一樣用於自定義註解,我能能夠經過@Scope自定義的註解來限定註解做用域,實現局部的單例;
  • @Singleton:@Singleton其實就是一個經過@Scope定義的註解,咱們通常經過它來實現全局單例。但實際上它並不能提早全局單例,是否能提供全局單例還要取決於對應的Component是否爲一個全局對象。

咱們提到@Inject和@Module均可以提供依賴,那若是咱們即在構造函數上經過標記@Inject提供依賴,有經過@Module提供依賴Dagger2會如何選擇呢?具體規則以下:函數

  • 步驟1:首先查找@Module標註的類中是否存在提供依賴的方法。性能

  • 步驟2:若存在提供依賴的方法,查看該方法是否存在參數。測試

    • a:若存在參數,則按從步驟1開始依次初始化每一個參數;
    • b:若不存在,則直接初始化該類實例,完成一次依賴注入。
  • 步驟3:若不存在提供依賴的方法,則查找@Inject標註的構造函數,看構造函數是否存在參數。ui

    • a:若存在參數,則從步驟1開始依次初始化每個參數
    • b:若不存在,則直接初始化該類實例,完成一次依賴注入。

Dagger2使用入門

一、案例A

Car類是需求依賴方,依賴了Engine類;所以咱們須要在類變量Engine上添加@Inject來告訴Dagger2來爲本身提供依賴。 Engine類是依賴提供方,所以咱們須要在它的構造函數上添加@Injectthis

public class Engine {

    /** * 二是用來標記構造函數,Dagger2經過@Inject註解能夠在須要這個類實例的時候來找到這個構造函數並把相關實例構造出來, * 以此來爲被@Inject標記了的變量提供依賴 */
    @Inject
    Engine(){}

    @Override
    public String toString() {
        return "Engine{}";
    }

    public void run(){
        System.out.println("引擎轉起來了~~~");
    }
}
複製代碼

接下來咱們須要建立一個用@Component標註的接口CarComponent,這個CarComponent其實就是一個注入器,這裏用來將Engine注入到Car中。spa

@Component
public interface CarComponent {
    void inject(Car car);
}
複製代碼

完成這些以後咱們須要Build下項目,讓Dagger2幫咱們生成相關的Java類。接着咱們就能夠在Car的構造函數中調用Dagger2生成的DaggerCarComponent來實現注入(這其實在前面Car類的代碼中已經有了體現)code

public class Car {
    /** * @Inject@Inject有兩個做用,一是用來標記須要依賴的變量,以此告訴Dagger2爲它提供依賴 */
    @Inject
    Engine engine;

    public Car() {
        DaggerCarComponent.builder().build().inject(this);
    }

    public Engine getEngine() {
        return this.engine;
    }

    public static void main(String ... args){
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngine());
    }
}
複製代碼

二、案例B

若是建立Engine的構造函數是帶參數的呢?好比說製造一臺引擎是須要齒輪(Gear)的。或者Eggine類是咱們沒法修改的呢?這時候就須要@Module和@Provide上場了。

一樣咱們須要在Car類的成員變量Engine上加上@Inject表示本身須要Dagger2爲本身提供依賴;Engine類的構造函數上的@Inject也須要去掉,應爲如今不須要經過構造函數上的@Inject來提供依賴了。

public class Engine {

    private String name;

    @Inject
    Engine(){}

    Engine(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Engine{" +
                "name='" + name + '\'' +
                '}';
    }

    public void run() {
        System.out.println("引擎轉起來了~~~");
    }
}
複製代碼

接着咱們須要一個Module類來生成依賴對象。前面介紹的@Module就是用來標準這個類的,而@Provide則是用來標註具體提供依賴對象的方法(這裏有個不成文的規定,被@Provide標註的方法命名咱們通常以provide開頭,這並非強制的但有益於提高代碼的可讀性)。

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    /** * 用於標註Module所標註的類中的方法,該方法在須要提供依賴時被調用,從而把預先提供好的對象當作依賴給標註了@Inject的變量賦值 * @return */
    @Provides
    Engine provideEngine(){
        return new Engine("gear");
    }
}
複製代碼

接下來咱們還須要對CarComponent進行一點點修改,以前的@Component註解是不帶參數的,如今咱們須要加上modules = {MarkCarModule.class},用來告訴Dagger2提供依賴的是MarkCarModule這個類。

@Component(modules = MarkCarModule.class)
public interface CarComponent {
    void inject(Car car);
}
複製代碼

Car類的構造函數咱們也須要修改,相比以前多了個markCarModule(new MarkCarModule())方法,這就至關於告訴了注入器DaggerCarComponent把MarkCarModule提供的依賴注入到了Car類中。

public class Car {
    /** * 咱們提到@Inject@Module均可以提供依賴,那若是咱們即在構造函數上經過標記@Inject提供依賴,有經過@Module提供依賴Dagger2會如何選擇呢?具體規則以下: * * 步驟1:首先查找@Module標註的類中是否存在提供依賴的方法。 * 步驟2:若存在提供依賴的方法,查看該方法是否存在參數。 * a:若存在參數,則按從步驟1開始依次初始化每一個參數; * b:若不存在,則直接初始化該類實例,完成一次依賴注入。 * * * 步驟3:若不存在提供依賴的方法,則查找@Inject標註的構造函數,看構造函數是否存在參數。 * a:若存在參數,則從步驟1開始依次初始化每個參數 * b:若不存在,則直接初始化該類實例,完成一次依賴注入 */
    @Inject
    Engine engine;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngine() {
        return this.engine;
    }

    public static void main(String ... args){
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngine());
    }
}
複製代碼

這樣一個最最基本的依賴注入就完成了,接下來咱們測試下咱們的代碼。 輸出

Engine{name='gear'}
複製代碼

三、案例C

那麼若是一臺汽車有兩個引擎(也就是說Car類中有兩個Engine變量)怎麼辦呢?不要緊,咱們還有@Qulifier!首先咱們須要使用Qulifier定義兩個註解:

public class Engine {

    /** * 用於自定義註解,也就是說@Qulifier就如同Java提供的幾種基本元註解同樣用來標記註解類。咱們在使用@Module來標註提供依賴的方法時,方法名咱們是能夠隨便定義的(雖然咱們定義方法名通常以provide開頭,但這並非強制的,只是爲了增長可讀性而已)。那麼Dagger2怎麼知道這個方法是爲誰提供依賴呢?答案就是返回值的類型,Dagger2根據返回值的類型來決定爲哪一個被@Inject標記了的變量賦值。可是問題來了,一旦有多個同樣的返回類型Dagger2就懵逼了。@Qulifier的存在正式爲了解決這個問題,咱們使用@Qulifier來定義本身的註解,而後經過自定義的註解去標註提供依賴的方法和依賴需求方(也就是被@Inject標註的變量),這樣Dagger2就知道爲誰提供依賴了。----一個更爲精簡的定義:當類型不足以鑑別一個依賴的時候,咱們就可使用這個註解標示 * 1. 使用@Qulifier定義兩個註解 */
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QualifierA { }
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface QualifierB { }

    private String name;

    Engine(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Engine{" +
                "name='" + name + '\'' +
                '}';
    }

    public void run() {
        System.out.println("引擎轉起來了~~~");
    }
}
複製代碼

同時咱們須要對依賴提供方作出修改

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    /** * 2. 同時咱們須要對依賴提供方作出修改 * @return */
    @Engine.QualifierA
    @Provides
    Engine provideEngineA(){
        return new Engine("gearA");
    }

    @Engine.QualifierB
    @Provides
    Engine provideEngineB(){
        return new Engine("gearB");
    }
}
複製代碼

接下來依賴需求方Car類一樣須要修改

public class Car {
    /** * 3. 接下來依賴需求方Car類一樣須要修改 */
    @Engine.QualifierA
    @Inject
    Engine engineA;

    @Engine.QualifierB
    @Inject
    Engine engineB;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngineA() {
        return this.engineA;
    }

    public Engine getEngineB() {
        return this.engineB;
    }

    public static void main(String... args) {
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngineA());
        System.out.println(car.getEngineB());
    }
}
複製代碼

執行結果:

Engine{name='gearA'}
Engine{name='gearB'}
複製代碼

四、案例D

接下來咱們看看@Scope是如何限定做用域,實現局部單例的。 首先咱們須要經過@Scope定義一個CarScope註解:

public class Engine {


    /** * 用於自定義註解,我能能夠經過@Scope自定義的註解來限定註解做用域,實現局部的單例 * 1. @Scope定義一個CarScope註解 */
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CarScope {
    }

    private String name;

    Engine(String name) {
        this.name = name;
        System.out.println("Engine create: " + name);
    }

    @Override
    public String toString() {
        return "Engine{" +
                "name='" + name + '\'' +
                '}';
    }

    public void run() {
        System.out.println("引擎轉起來了~~~");
    }
}
複製代碼

接着咱們須要用這個@CarScope去標記依賴提供方MarkCarModule。

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    /** * 2. @CarScope去標記依賴提供方MarkCarModule * @return */
    @Engine.CarScope
    @Provides
    Engine provideEngine(){
        return new Engine("gear");
    }

}
複製代碼

同時還須要使用@Scope去標註注入器Compoent

/** * 3. 同時還須要使用@Scope去標註注入器Compoent */
@Engine.CarScope
@Component(modules = MarkCarModule.class)
public interface CarComponent {
    void inject(Car car);
}
複製代碼
public class Car {

    @Inject
    Engine engineA;

    @Inject
    Engine engineB;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngineA() {
        return this.engineA;
    }

    public Engine getEngineB() {
        return this.engineB;
    }

    public static void main(String... args) {
        //TODO:
        Car car = new Car();
        System.out.println(car.getEngineA());
        System.out.println(car.getEngineB());
    }
}
複製代碼

若是咱們不適用@Scope,上面的代碼會實例化兩次Engine類,所以會有兩次"Create Engine"輸出。如今咱們在有@Scope的狀況測試下勞動成果: 輸出

Engine create: gear
Engine{name='gear'}
Engine{name='gear'}
複製代碼

最後

讀到這的朋友以爲不錯能夠點贊關注下,感謝您的支持,之後會不停更新更多精選乾貨及資訊分享,歡迎你們在評論區留言討論!

相關文章
相關標籤/搜索