初識dagger(一)

焦世春發表於2019-03-02

依賴注入

依賴注入:在專案開發中,常見一個類的例項依賴另一個類例項的情況 如

public  class Man(){
	prviate Car car;
	public Man(){
		car=new Car();
	}
}
複製程式碼

常見的依賴方式基於構造方法,set設定等

依賴注入是控制反轉的一種表現形式,被依賴的物件(Car)不再由 依賴物件(Man)主動建立 ,而是由第三方容器去建立,然後注入到依賴物件(man)的過程,降低程式碼耦合性

整合與使用

在build.gradle中新增依賴:

dependencies {
    ...
    implementation 'com.google.dagger:dagger:2.13'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
}
複製程式碼

藉助上面的場景進行應用

Car相關程式碼 由於Car需要被注入,在構造方法上增加 @Inject 註解

public class Car {
  private CarDipan carDipan;

  @Inject
  public Car() {
  }

  public void pao() {
    System.out.println("跑起來");
  }
}
複製程式碼

Man相關程式碼 在Car變數上宣告 @Inject 註解

public class Man {
  @Inject
  Car car;

  public Man() {
    //該行程式碼後面講到
    DaggerManComponent.create().inject(this);
  }

  public void kaiche() {
    car.pao();
  }
}
複製程式碼

此時依賴和被依賴的類都建立好了,還缺少一個紐帶把他們連線起來,紐帶就是 @Component
@Component 註解需要作用到介面或者抽象類上,新增抽象方法,引數是需要注入依賴的類,會自動生成以Dagger開頭的實現類 在Man的構造方法中進行注入 DaggerManComponent.create().inject(this); (可能會報找不到該類,需要rebuild下) ManComponent程式碼如下

@Component
interface ManComponent {
  void inject(Man man);
}

複製程式碼

測試程式碼

public class DaggerTest {
  public static void main(String[] args) {
    Man man = new Man();
    man.kaiche();
  }
}
複製程式碼

Module和Provides註解

有一些第三方庫,我們無法修改原始碼,即無法用@Inject去註解構造方法。這時候就需要@Module和@Provides註解

  • @Module 註解到類上,表示這個類可以作為一個模組去引入到被@Component註解的介面上,規定用@Module註解的類名以Module做字尾
  • @Provides用於被@Module標註類的方法中,規定用provide作為字首
    假設Man物件包含一個FangZi物件,該物件構造方法無法用@Inject註解標註,那麼可以提供以下類
@Module
public class FangModule {
  @Provides
  public Fang provideFang() {
    return new Fang();
  }
}
複製程式碼

對應的ManComponent類也需要修改

@Component(modules = FangModule.class)
interface ManComponent {
  void inject(Man man);
}
複製程式碼

這樣Man裡面的房子屬性就會成功注入。被注入的屬性一定要用@Inject註解
Module類的構造方法可以帶有引數,需要注意的是當Module類有帶有引數的構造方法時, DaggerManComponent不會有Create方法,需要通過DaggerManComponent.builder().fangModule(new FangModule(引數)).build()手動傳入module物件

Bind註解

Bind註解和Provides注意類似 ,也是放到被Module類的方法上,不同的是Bind註解的方法是抽象方法,引數型別是返回值型別的子類。 用於注入某個介面或者抽象類時,可以獲取他的實現子類。或者可以說將一種型別轉換成他的父類或介面,只暴露父類或介面的方法

BindModule.java

@Module
public abstract class BindModule {
  @Binds
  public abstract Manger bindManager(ManagerImpl manager);
  
  public static abstract class Manger {
    public abstract String getName();
  }

  public static class ManagerImpl extends Manger {
    @Inject
    public ManagerImpl() {
    }

    @Override public String getName() {
      return "我是ManagerImpl";
    }
  }
}
Man.java

public class Man {

  @Inject
  BindModule.Manger manger;//這是獲取的時他的子類MangerImpl

  public Man() {
    DaggerManComponent.create().inject(this);
  }

  public void run() {
    System.out.println(manger.getName());
  }
}
複製程式碼

Named、Singleton和Reusable

@Named 別名註解 和@Provides配合使用 比如我有兩套房子,但是返回值都是房子 如果不用@Named註解,dagger不知道我們們要哪個房子,就會迷失,這時就需要起一個別名

@Module
public class FangModule {

  @Named("北三環")
  @Provides
  public Fang provideFang() {
    return new Fang("北三環");
  }
  @Named("西四環")
  @Provides
  public Fang provideFang2() {
    return new Fang("西四環");
  }
}
複製程式碼

在獲取房子是就必須註明@Named 表示 要哪一個

public class Man {
  @Inject
  Car car;
  @Named("北三環")
  @Inject
  Fang fang1;
  @Named("西四環")
  @Inject
  Fang fang2;
}
複製程式碼

@Qualifier 元註解 註解在@Named上 我們可以用 Qualifier自定義和 @Named功能相同的 註解
@Singleton 單例註解 在一個Component元件中單例 Singleton標註在Component上 和 Provides標註的方法或者標註在要注入的類上。 實現機制 是通過DoubleCheck來保證單例,在生成的DaggerManComponent類的初始化中,會把Provider轉換為DoubleCheck 在DoubleCheck的get方法中保證例項唯一

@Module
public class FangModule {
  @Singleton
  @Provides
  public Fang provideFang() {
    return new Fang("北三環");
  }
}
複製程式碼
@Singleton
@Component(modules = FangModule.class)
interface ManComponent {
  void inject(Man man);
}
複製程式碼

@Scope 元註解 註解在@Singleton 我們可以用 @Scope自定義和 @Singleton功能相同的 註解
@Reusable 註解 有時我們為了限制物件的建立次數,使用時可以從快取中獲取物件,可以使用該註解 該註解標註在 Provides標註的方法或者標註在要注入的類上。 該機制會把Provider轉換為SingleCheck來 使用SingleCheck來保證如果無物件會建立物件 如果有則會從快取中獲取

@Module
public class FangModule {
  @Reusable
  @Provides
  public Fang provideFang() {
    return new Fang("北三環");
  }
}
複製程式碼
@Component(modules = FangModule.class)
interface ManComponent {
  void inject(Man man);
}  
複製程式碼

Lazy和Provider

Lazy 有時候我們不想執行時就把類建立好,而是在需要的時候建立類,這是就可以通過Lazy包裝要被注入的成員 ,使用時通過get方式獲取

public class Man {
  @Inject
  Lazy<Car> car;

  public Man() {
    DaggerManComponent.create().inject(this);
  }

  public void kaiche() {
    car.get().pao();
  }
} 
複製程式碼

Provider 有時我們 需要建立類的多個例項,可以通過Provider包裝要被注入的成員,每次執行get則會建立一個新的例項

IntoSet和IntoMap註解

IntoSet需要和Provides或Bind一起使用,我們知道在Module中如果有兩個方法返回值會導致依賴迷失,需要用Named註解起別名。 但是如果這些方法被@IntoSet註解修飾,相同的返回值產生的物件會被放到同一個Set集合中。同時注入該類物件時,需要用Set去包裝

FangModule.java

@Module
public class FangModule {
  @IntoSet
  @Provides
  public Fang provideFang1() {
    return new Fang("國貿");
  }

  @IntoSet
  @Provides
  public Fang provideFang2() {
    return new Fang("望京");
  }
}

Man.java

public class Man {
  @Inject
  Set<Fang> fangs;

  public Man() {
    DaggerManComponent.builder().fangModule(new FangModule()).build().inject(this);
  }

  public void run() {
    fangs.forEach(Fang::get);
  }
}
複製程式碼

IntoMap和IntoSet類似,不過是將發返回的物件放入到map集合中。用IntoMap註解時, 必須指明Key.使用 @IntKey/ @LongKey/@StringKey註解 也可以使用@MapKey元註解,自定義Key註解