天天看點

依賴注入神器:Dagger2詳解系列依賴注入神器:Dagger2詳解系列Dagger2的使用

依賴注入神器:Dagger2詳解系列

序言

Dagger2是啥

Dagger2是啥,Google告訴我們:

Dagger is a fully static, compile-time dependency injection framework for both Java and Android. It is an adaptation of an earlier versioncreated by Square and now maintained by Google.

Dagger aims to address many of the development and performance issues that have plagued reflection-based solutions. More details can be found in this talk(slides) by +Gregory Kick.

那麼長一段話,看的我是淚流滿面:

Dagger是為Android和Java平台提供的一個完全靜态的,在編譯時進行依賴注入的架構,原來是由Square公司維護的然後現在把這堆東西扔給Google維護了。Dagger解決了基于反射帶來的開發和性能上的問題(因為Dagger并沒有用反射來做依賴注入)blabla。。。。說了那麼多,其實就是告訴我們這家夥可以用來搞依賴注入哦

依賴注入,搞過Spring的人肯定都知道這是啥,SpringMVC裡用到了大量依賴注入,鑒于gay們都是做Android,讓我們共飲此杯,聊一聊依賴注入。

依賴注入

我們在做項目時,經常需要在一個對象裡去建立另一個對象的示例,這種行為是産生耦合的常見形式,對于一個大型項目來說,過多的互相依賴會導緻代碼難以維護,很容易就會碰到修改一個小需求需要大面積的修改各種代碼,特别是代碼原來不是自己維護的,撸着代碼的你就開始問候别人家的親友了。。。。

舉個栗子

我們現在有家Coffee Shop

這個是我們的業務核心咖啡機CoffeeMachine

/**
 * 這是一個制作Coffee的例子
 * CoffeeMaker是對制作Coffee過程的一個封裝
 * 制作Coffee需要實作CoffeeMarker的makeCoffee方法
 */
public class CoffeeMachine {
    private CoffeeMaker maker;

    public CoffeeMachine(){
        maker = new SimpleMaker();
    }

    public String makeCoffee(){
        return maker.makeCoffee();
    }
}
           

CoffeeMaker

public interface CoffeeMaker {
    String makeCoffee();
}
           

SimpleCoffeeMaker

//這個是自動咖啡機
public class SimpleMaker implements CoffeeMaker {
    @Override
    public String makeCoffee() {
        return  "Coffee is made by SimperMarker";
    }
}
           

小店新開張,我們的咖啡都是咖啡機做出來的,物美價廉啦。。。。

這家CoffeeShop很簡單,在CoffeeMachine中可以看到,CoffeeMachine持有了一個CoffeeMaker接口,而具體制作Coffee的過程是由實作了CoffeeMaker的自動咖啡機SimpleMaker實作的,CoffeeMaker是在構造方法中new 出了一個實作CoffeeMaker接口的SimpleCoffee。目前的功能很簡單,這麼寫看着也沒什麼問題。

随着業務的擴充,消費人群改變了,自動咖啡機也完全不能滿足現有客戶的需求,這個時候我們的CoffeeShop該進行業務更新咯 。

經過董事會決定,公司決定投入大筆資金,雇傭咖啡師來制作咖啡。

我們的咖啡師:

public class Cooker {
    String name; //咖啡師名字
    String coffeeKind; //制作咖啡的類型

    public Cooker(String name,String coffeeKind){
        this.name = name;
        this.coffeeKind = coffeeKind;
    }

    public String make(){
        return name +" make " + coffeeKind; //咖啡師制作Coffee的過程
    }
}
           

這時候SimpleMarker更新了 :

public class SimpleMaker implements CoffeeMaker {
    Cooker cooker;  //現在需要咖啡師來制作咖啡了

    public SimpleMaker(Cooker cooker){
        this.cooker = cooker;
    }

    @Override
    public String makeCoffee() {
        return cooker.make();
    }
}
           

基于目前的情況,我們制作咖啡的流程發生了變化,原來的業務随着Cooker的加入發生了改變,但細心的小夥伴會發現目前還有一個地方受到了影響,那就是我們的CoffeeMachie,看這段代碼:

private CoffeeMaker maker;

//構造方法  
public CoffeeMachine(Cooker cooker){
    maker = new SimpleMaker(cooker);
}
           

我們的SimpleMake更新了,業務波動影響到了我們的CoffeeMachine,這時候不得不對CoffeeMachine也進行修改:

public class CoffeeMachine {
    private CoffeeMaker maker;

    public CoffeeMachine(Cooker cooker){
        maker = new SimpleMaker(cooker);
    }

    public String makeCoffee(){
        return maker.makeCoffee();
    }
}
           

這時候我們的CoffeeMachine就懵逼了,你的業務更新就更新呗,為毛我制造CoffeeMachine的過程也要變動,非常的不願意。。但迫于老闆的壓力。最後還是給整改了。很明顯,這是一個不合适的流程,

簡單的一個業務的更新,還要找我們的機器制造廠來幫你修改,那如果業務非常複雜,引用了SimpleMaker的可不僅僅是CoffeeMachine一個,那是不是每個引用的地方都需要進行修改,業務龐大的情況下,

這種修改就是緻命的,不僅需要做大量沒有意義的體力勞動來修改,還可能導緻大片業務代碼的變動直接增加測試的成本,其他接收這個需求的開發GG直接得跪鍵盤了,一個SimpleMakere的

改動對CoffeeMachine産生了直接的影響,肯定有什麼地方是不對的。原因就是CoffeeMachine裡的CoffeeMaker是自己new出來的。這就是一個很不好的地方。

這種糟糕的執行個體引用的方式我們稱之為硬初始化(Hard init),和寫死(Hard coding)一樣,都是糟糕代碼滋生的好方法,Hard init不僅增加了各個子產品的耦合,還讓單元測試變得更加困難了。

那麼該用什麼方法來盡量地降低各個子產品的耦合,避免new對象帶來的問題呢。我們知道,類的初始化可以描述成new,get和set,new就是我們上面說的Hard init,容易增加各個子產品之間的耦合,而

get,則可以看做是工廠模式,工廠模式是new的一個更新版本,相對硬初始化來說,工廠模式把對象的建立都集中在工廠裡了,對于需要依賴的類來說,無需再考慮對象的 建立工作了,隻需要關注

如何從工廠裡獲得,在發生修改時也不會有太多的改動,和以前的方案比起來要好了不少。但工廠模式的對象建立依然非常的不靈活,對象的實作完全取決于工廠,會導緻原來的依賴由具體的對象變

為相應的工廠,本質上還是有依賴關系的!!!對,工廠模式并沒有改變本質的依賴關系,而且,對于簡單職責的工廠來說,抽出一層工廠似乎并不會太麻煩,但當我們的工廠中提供的類的實作複雜

起來時,又回到了最初的問題上,我們是在工廠中new對象還是繼續給工廠中的内容再提供一個工廠呢,這種層層嵌套會讓我們的代碼變得幹澀難懂,也會有設計過度的嫌疑。。。說了半天,工廠模

式在解決依賴問題還是有點尴尬呀。那麼最後還有一個set,也就是我們的依賴注入了,依賴注入的依賴是從外部傳遞過來的,而且在Java平台上很多時候都是通過反射或者動态編譯來提供依賴注入,

這樣就更加剝離的各個部分的耦合性,也讓上述兩種方式隻能望其項背了。

先将依賴注入的最基本的原理說明白,依賴注入主要有三種途徑,eg

public class CoffeeMachinWithInjection implements InjectMaker{
    private CoffeeMaker maker;

    /*依賴注入的3種常見形式
     *No.1  構造函數注入
     */
    public CoffeeMachinWithInjection(CoffeeMaker maker){
        this.maker = maker;
    }

    //No.2  Setter注入
    public void setMaker(CoffeeMaker maker){
        this.maker = maker;
    }

    // //No.3 接口注入
    @Override
    public void injectMaker(CoffeeMaker maker) {
        this.maker = maker;
    }

    public String makeCoffee(){
        return maker.makeCoffee();
    }

}
           

其中,InjectMarker接口内容如下:

public interface InjectMaker {
    void injectMaker(CoffeeMaker maker);
}
           

是不是很簡單,說白了就是不要在需要依賴的類中通過new來建立依賴而是通過方法提供的參數注入進來,這樣我們的需要依賴的類和提供依賴的類的實作方法分隔開了,一切又變得如此美好咯。

不過這種手動提供依賴也是很繁雜的工作,充滿的濃濃的重複體力勞動的氣息,如何來盡量減少這些備援代碼的制作呢,答案就是Dagger!

第一部分到此結束。

Dagger2的使用

上一篇我們簡單介紹了依賴注入是什麼,并用簡單的栗子說明了依賴注入的使用,本篇主要來分享Dagger2的簡單使用,為運用到項目中做進一步的鋪墊。

引入Dagger2

首先,我們需要将Dagger2的依賴寫入我們的gradle中,具體配置如下:

apply plugin: 'com.neenbedankt.android-apt'

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}

dependencies {
    apt 'com.google.dagger:dagger-compiler:2.0'
    compile 'com.google.dagger:dagger:2.0'
}
           

把 apply plugin和dependencies放在app的gradle裡,把buildscript放在項目的gradle裡即可,之後我們就可以開始Dagger之旅了。

注解的使用

看過Dagger2的人應該都知道,Dagger2是通過注解來實作依賴注入的,是以,在使用Dagger2之前,我們需要了解這些注解的含義,如果對注解是什麼還不清楚的同學可以Google一下,在這就不細說了。Dagger2中主要有6種注解,我們把它拆為4+2,前四種通俗易懂,後兩種了解起來就有一定難度了。

四個基礎

這裡說的四個基礎,指的是四種基礎的注解,他們分别是:

- @Inject Inject主要有兩個作用,一個是使用在構造函數上,通過标記構造函數讓Dagger2來使用(Dagger2通過Inject标記可以在需要這個類執行個體的時候來找到這個構造函數并把相關執行個體new出來)進而提供依賴,另一個作用就是标記在需要依賴的變量讓Dagger2為其提供依賴。

  • @Provide 用Provide來标注一個方法,該方法可以在需要提供依賴時被調用,進而把預先提供好的對象當做依賴給标注了@Injection的變量指派。provide主要用于标注Module裡的方法
  • @Module 用Module标注的類是專門用來提供依賴的。有的人可能有些疑惑,看了上面的@Inject,需要在構造函數上标記才能提供依賴,那麼如果我們需要提供的類構造函數無法修改怎麼辦,比如一些jar包裡的類,我們無法修改源碼。這時候就需要使用Module了。Module可以給不能修改源碼的類提供依賴,當然,能用Inject标注的通過Module也可以提供依賴
  • @Component Component一般用來标注接口,被标注了Component的接口在編譯時會産生相應的類的執行個體來作為提供依賴方和需要依賴方之間的橋梁,把相關依賴注入到其中。

這些标注看起來可能比較抽像,為了友善各位了解,送圖一張來說明這些标注的作用和之間的關系:

依賴注入神器:Dagger2詳解系列依賴注入神器:Dagger2詳解系列Dagger2的使用

圖檔主要分為三部分,左邊的是依賴提供者,比如我們用Module标注的類或者用Injection标注的構造函數,右邊的是依賴的需求方,例如我們用inject标注的變量,而Component則是連接配接兩者的橋梁,Component從依賴提供者提供依賴,并把這些依賴注入相關的類中,Dagge正如其名,就像把匕首讓依賴能夠非常犀利的注入到需要它的地方。

說了那麼多前言,雖然這些注解都有各自獨特的作用,單用起來其實很簡單,接下來我們将進一步地講解這些标注的作用,just show you code。在使用之前,隻要大緻明白這些标注的意義就行了,簡單的依賴注入通過這幾個标注就能完成。

簡單的栗子

還是從我們的CoffeeShop說起,現在,我們有一個Activity:

public class OriginActivity extends Activity implements View.OnClickListener{
    @BindView(R.id.btnMakeCoffee)
    Button btnMakeCoffee;
    @BindView(R.id.tvCoffee)
    TextView tvCoffee;

    CoffeeMachine coffeeMachine;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple);
        ButterKnife.bind(this);
        Cooker cooker = new Cooker("James","Espresso");
        coffeeMachine = new CoffeeMachine(cooker);
        initView();
    }

    private void initView(){
        btnMakeCoffee.setOnClickListener(this);
    }

    private String makeCoffee(){
        return coffeeMachine.makeCoffee();
    }


    @Override
    public void onClick(View v) {
        if(R.id.btnMakeCoffee == v.getId()){
            tvCoffee.setText(makeCoffee());
        }
    }
}
           

可以看到,我們的CoffeeMachine還是通過在在onCreate中new出來的,這就是上一篇中說的不好的味道,我們試着用Dagger2來注入這些依賴。

需要注入依賴的是: CoffeeMachine coffeeMachine,怎麼讓Dagger2知道這個東西需要注入依賴呢,很簡單,

@Inject
 CoffeeMachine coffeeMachine;
           

是不是和上面ButterKnif給View注入的很相似,就是醬紫。這就是我們的依賴需求方。我們還需要依賴提供方

上面提到過,提供依賴可以給相關依賴類的構造函數添加@Inject注解,這樣Dagger2就能找到它并用來提供依賴,是以我們看CoffeeMachie這個類

public class CoffeeMachine {
    private CoffeeMaker maker;

    public CoffeeMachine(Cooker cooker){
        maker = new SimpleMaker(cooker);
    }

    public String makeCoffee(){
        return maker.makeCoffee();
    }
}
           

這個時候我們需要給他的構造函數增加一個@Inject

@Inject
 public CoffeeMachine(Cooker cooker){
        maker = new SimpleMaker(cooker);
    }
           

不過看起來還是不太對,為啥,因為我們在它的構造函數裡又new了一個對象,這樣又是不好的味道,這個maker應該被注入進去。

我們再讓Dagger2來提供CoffeeMaker的依賴,還是像剛才一樣,我們去修改SimpleMaker的構造函數,不過在此之前,需要先修改一下CoffeeMachine的構造函數

public class CoffeeMachine {
    private CoffeeMaker maker;

    public CoffeeMachine(CoffeeMaker maker){
        this.marker = maker
    }

    public String makeCoffee(){
        return maker.makeCoffee();
    }
}
           

然後我們再修改SimpleMaker

public class SimpleMaker implements CoffeeMaker {
    Cooker cooker;

    @Inject
    public SimpleMaker(Cooker cooker){
        this.cooker = cooker;
    }

    @Override
    public String makeCoffee() {
        return cooker.make();
    }
}
           

這次變聰明了,SimpleMaker的構造函數中Cooker的依賴也是注入進來的,這個時候我們還需要提供Cooker的依賴

but,先打住一下,我們前面說了,除了構造函數提供依賴,還能用Module提供,是以,機智的我們這次用Module來提供依賴。

我們需要建立一個Module:

@Module
public class SimpleModule {

    @Provides
    Cooker provideCooker(){
        return new Cooker("James","Espresso");
    }
}
           

Module的寫法是不是很簡單,隻要提供一個Module類,給類打上@Module的注解,然後再添加一個提供Cooker依賴的provideCooker方法,傳回一個new Cooker(“James”,”Espresso”)即可,當然provideCooker需要添加@Provide注解這樣Dagger2才能在需要Cooker的時候找到它來提供毅力啊,這裡有個小細節,

我們需要提供Cooker的依賴,傳回類型是Cooker就行了,方法名叫什麼都行,但最好是以provide開頭,這樣能增加代碼的可讀性。

提供依賴的東西都寫完了,最後需要一個把依賴注入到需要依賴的地方,這個工具就是Component

@Component(modules = SimpleModule.class)
public interface SimpleComponent {
    void inject(SimpleActivity simpleActivity);
}
           

用@Component注解這個接口能讓Dagger2在需要注入時找到這個工具,同時還告訴Dagger2提供依賴的是SimpleModule這個類

當然,如果需要用這個Module給SimpleActivity注入我們的CoffeeMachine,還需要一個inject方法,裡面傳入我們的SimpleActivity對象的執行個體。

這裡需要注意,我們必須傳入SimpleActivity自己,不能把SimpleActivity的父類定義到方法裡作為參數來接收SimpleActivity,從文法上來說是正确的,但你會發現這樣做沒辦法完成注入,這是Dagger2的一個坑,别給踩着了。

注入器也制作完了,就剩下最後一步:“注入”。

這步也很簡單,隻需要在我們的onCreate裡這麼寫:

private SimpleComponent simpleComponent;

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        simpleComponent = DaggerSimpleComponent.builder().simpleModule(getModule()).build();
        simpleComponent.inject(this);
    }    
           
這裡有個地方需要注意一下,DaggerSimpleComponent是什麼,我們壓根就沒寫這個東西。對,這就是Dagger2在編譯時生成的類,Dagger2就是通過這些編譯生成的東西完成了依賴的注入,那麼Dagger2在編譯時産生的類是怎麼工作的呢,我們将在下一篇來叙述這個問題。

這裡再說明一個問題,我們有兩種方式可以提供依賴,一個是注解了@Inject的構造方法,一個是在Module裡提供的依賴,那麼Dagger2是怎麼選擇依賴提供的呢,規則是這樣的:

- 步驟1:查找Module中是否存在建立該類的方法。

- 步驟2:若存在建立類方法,檢視該方法是否存在參數

- 步驟2.1:若存在參數,則按從步驟1開始依次初始化每個參數

- 步驟2.2:若不存在參數,則直接初始化該類執行個體,一次依賴注入到此結束

- 步驟3:若不存在建立類方法,則查找Inject注解的構造函數,看構造函數是否存在參數

  • 步驟3.1:若存在參數,則從步驟1開始依次初始化每個參數
  • 步驟3.2:若不存在參數,則直接初始化該類執行個體,一次依賴注入到此結束

概括一下就是從注解了@Inject的對象開始,從Module和注解過的構造方法中獲得執行個體,若在擷取該執行個體的過程中需要其他類的執行個體,則繼續擷取被需要類的執行個體對象的依賴,同樣是從Module和标注過的構造方法中擷取,并不斷遞歸這個過程直到所有被需要的類的執行個體建立完成,在這個過程中Module的優先級高于注解過的構造方法。明白了這個原理之後老奶奶都會用這幾個标簽了。

@Scope和@Qulifier

上面把四個簡單的注解的用法都講完了,但很多時候這幾個注解并不能涵蓋我們所有的場景,這時就需要@Scope和@Qulifier來幫忙了。

有的同學可能在用Module的時候會有疑惑,為什麼方法怎麼命名都行,那時怎麼區分它為誰提供依賴呢。答案是根據傳回類型來确定的,當某個對象需要注入依賴時,Dagger2就會根據Module中标記了@Provide的方法的傳回值來确定由誰為這個變量提供執行個體。那問題來了,如果有兩個一樣的傳回類型,該用誰呢。我們把這種場景叫做依賴迷失,見名知意,Dagger這時候就不知道用誰來提供依賴,自然就迷失了。是以我們引入了@Qulifier這個東西,通過自定義Qulifier,可以告訴Dagger2去需找具體的依賴提供者。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface A {}
           
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface B {}
           

我們定義了兩個注解,@A和@B,他們都是用@Qulifiier标注的

再看看我們的Module

@Module
public class SimpleModule {

    @Provides
    @A
    Cooker provideCookerA(){
        return new Cooker("James","Espresso");
    }


    @Provides
    @B
    Cooker provideCookerB(){
        return new Cooker("Karry","Machiato");
    }

}
           

再看看具體的使用

public class ComplexMaker implements CoffeeMaker {
    Cooker cookerA;
    Cooker cookerB;

    @Inject
    public ComplexMaker(@A Cooker cookerA,@B Cooker cookerB){
        this.cookerA = cookerA;
        this.cookerB = cookerB;
    }

    @Override
    public String makeCoffee() {
        return cooker.make();
    }
}
           
cookerA.make();//James make Espresso
cookerB.make();//Karry make Machiato
           

這樣說是不是很簡單,相信大家很快就能了解@Qulifier的作用和用法

接着說@Scope,@Scope就要難了解點了,繼續來看栗子,我們定義一個Scope注解

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {}
           

我們把定義的@PerActivity用到Module裡

““

@Module

public class ActivityModule {

@Provides
CoffeeShop provideCoffeeShop(){
    return CoffeeShop.getInstance();
}

@Provides
@PerActivity
CookerFactory provideCookerFactory(){
    return new CookerFactory();
}

@Provides
CookerFactoryMulty provideCookerFactoryMulty(){
    return new CookerFactoryMulty();
}
           

}

““

這個Module提供了CoffeeShop,CookerFactory和CookerFacotryMulty的依賴

public class CoffeeShop {
    private static CoffeeShop INSTANCE;

    private CoffeeShop(){
        Log.d("TAG","CoffeeShop New Instance");
    }

    public static CoffeeShop getInstance(){
        if(INSTANCE == null){
            INSTANCE = new CoffeeShop();
        }
        return INSTANCE;
    }
}
           
public class CookerFactory {

    public CookerFactory(){
        Log.d("TAG","CookerFactory New Instance");
    }
}
           
public class CookerFactoryMulty {

    public CookerFactoryMulty(){
        Log.d("TAG","CookerFactoryMulty New Instance");
    }
}
           

我們在這三個對象的構造方法裡都加了Log,當他們的執行個體産生時能看到相關的Log,再看我們用到的地方,在MainActivity裡給每個類都寫兩個變量,

public class MainActivity extends Activity {

    ActivityComponent activityComponent;

    @Inject
    CoffeeShop coffeeShop1;

    @Inject
    CoffeeShop coffeeShop2;

    @Inject
    CookerFactory cookerFactory1;

    @Inject
    CookerFactory cookerFactory2;

    @Inject
    CookerFactoryMulty cookerFactoryMulty1;

    @Inject
    CookerFactoryMulty cookerFactoryMulty2;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        activityComponent = DaggerActivityComponent.builder()
                .activityModule(provideModule())
                .applicationComponent(MyApplication.getComponent()).build();
        activityComponent.inject(this);
        coffeeFactory.run();
    }

    private ActivityModule provideModule(){
        return new ActivityModule();
    }
}
           

ok,來看下運作的結果把

- ::    -/? D/TAG﹕ CoffeeShop New Instance
- ::    -/? D/TAG﹕ CookerFactory New Instance
- ::    -/? D/TAG﹕ CookerFactoryMulty New Instance
- ::    -/? D/TAG﹕ CookerFactoryMulty New Instance
           

從Log中可以看到,CoffeeShop和CookerFactory的類都隻new過一次,而CookerFactoryMulty被new了兩次

再回頭看我們的Module,其中CoffeeShop的依賴是通過單例模式提供的,隻打一條Log很容易了解,而CookerFactory相對于CookerFactoryMulty來說内容幾乎是一模一樣,隻多加一個@PerActivity的注解,但卻比它少打了一次Log,這是為什麼呢。哈哈,客官們,這就是@Scope神秘的地方,他通過自定義@Scope注解提供了單例,正如上面的CookerFactory,雖然并未用單例來提供依賴,但卻和用單例提供依賴的CoffeeShop一樣,兩個對象的執行個體都是同一個,這就是Scope的作用,提供局部單例的功能,局部範圍是啥,那就是它生命周期範圍内。

OK,上面所有注解的作用和用法都說完了,再回頭看看是不是超級簡單,這些東西并沒有想象中的那麼難懂,隻要結合上面的栗子,我相信大家都能明白了。其實dagger2正真的難點并不是了解這些注解的作用和用法,而是如何在我們項目中來靈活地運用它。要做到這點,光靠這兩篇内容是不夠的,我們除了了解這些注解,還要明白它們是如何工作的,比如Scope為什麼能實作局部單例等,這是要通過源碼分析才能明白,同時,要運用到項目中,我們還得明白如何确定我們的最小粒度,Component,Module是怎麼劃分的等,這就涉及到Component的依賴,繼承等内容,在接下來的篇幅中,我将通過源碼分析和項目實戰,來繼續詳解Dagger2的用法,敬請期待。