Dagger 2 完全解析系列:
Dagger 2 完全解析(一)共屈,Dagger 2 的基本使用與原理
Dagger 2 完全解析(二)输硝,進階使用 Lazy今瀑、Qualifier、Scope 等
Dagger 2 完全解析(三)点把,Component 的組織關系與 SubComponent
Dagger 2 完全解析(四)橘荠,Android 中使用 Dagger 2
Dagger 2 完全解析(五),Kotlin 中使用 Dagger 2
Dagger 2 完全解析(六)郎逃,dagger.android 擴展庫的使用
上面文章地址是我博客上的地址哥童,本系列文章是基于 Google Dagger 2.11-rc2 版本
依賴注入
什么是依賴
如果在 Class A 中,有 Class B 的實例褒翰,則稱 Class A 對 Class B 有一個依賴贮懈。例如 Man 中有用到一個 Car 對象匀泊,即 Man 對 Car 有一個依賴。
public class Man {
Car car;
public Man() {
car = new Car();
}
...
}
上面這種寫法是最常見的寫法朵你,但是在下面幾個場景中存在一些問題:
如果要修改 Car 的構造函數各聘,例如需要使用
car = new Car(name)
的方式構造時,還要修改 Man 的代碼抡医;如果想測試不同的 Car 對 Man 的影響會很困難躲因,例如單元測試中使用 mock 的 car 測試 Man。
什么是依賴注入
依賴注入(Dependency Injection忌傻,簡稱 DI)是用于實現控制反轉(Inversion of Control大脉,縮寫為 IoC)最常見的方式之一,控制反轉是面向對象編程中的一種設計原則水孩,用以降低計算機代碼之間耦合度镰矿。控制反轉的基本思想是:借助“第三方”實現具有依賴關系的對象之間的解耦俘种。一開始是對象 A 對 對象 B 有個依賴衡怀,對象 A 主動地創(chuàng)建 對象 B,對象 A 有主動控制權安疗,實現了 Ioc 后抛杨,對象 A 依賴于 Ioc 容器,對象 A 被動地接受容器提供的對象 B 實例荐类,由主動變?yōu)楸粍硬老郑虼朔Q為控制反轉。注意玉罐,控制反轉不等同于依賴注入屈嗤,控制反轉還有一種實現方式叫“依賴查找”(Denpendency Lookup)。更多控制反轉的信息請看控制反轉的維基百科吊输。
依賴注入就是將對象實例傳入到一個對象中去(Denpendency injection means giving an object its instance variables)饶号。依賴注入是一種設計模式,降低了依賴和被依賴對象之間的耦合季蚂,方便擴展和單元測試茫船。
依賴注入的實現方式
其實在平常編碼的過程中,已經不知覺地使用了依賴注入
- 基于構造函數扭屁,在構造對象時注入所依賴的對象算谈。
public class Man {
Car car;
public Man(Car car) {
this.car = car;
}
...
}
- 基于 set 方法,使用 setter 方法來讓外部容器調用傳入所依賴的對象料滥。
public class Man {
...
public void setCar(Car car) {
this.car = car;
}
}
- 基于接口然眼,使用接口來提供 setter 方法。
public interface CarInjector {
void injectCar(Car car);
}
public class Man implements CarInjector {
...
@Override
public void injectCar(Car car) {
this.car = car;
}
}
- 基于注解葵腹,Dagger 2 依賴注入框架就是使用
@Inject
完成注入高每。
public class Man {
@Inject
Car car;
...
}
Dagger 2
Dagger 2 是 Java 和 Android 下的一個完全靜態(tài)屿岂、編譯時生成代碼的依賴注入框架,由 Google 維護鲸匿,早期的版本 Dagger 是由 Square 創(chuàng)建的雁社。
Dagger 2 是基于 Java Specification Request(JSR) 330標準。利用 JSR 注解在編譯時生成代碼晒骇,來注入實例完成依賴注入霉撵。
下面是 Dagger 2 的一些資源地址:
Github:https://github.com/google/dagger
官方文檔:https://google.github.io/dagger//
API:http://google.github.io/dagger/api/latest/
Dagger 2 的基本使用
上面介紹了依賴注入和 Dagger 2,下面由簡單的示例開始一步一步地解析 Dagger 2 的基本使用與原理洪囤。
引入 Dagger 2
在build.gradle
中添加依賴:
dependencies {
...
compile 'com.google.dagger:dagger:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
}
如果 Android gradle plugin 的版本低于2.2
徒坡,還需要引入 android-apt 插件。
使用 @Inject 標注需要注入的依賴
繼續(xù)使用上面 Man 的例子:
public class Man {
@Inject
Car car;
...
}
使用javax.inject.Inject
注解來標注需要 Dagger 2 注入的依賴瘤缩,build 后可以在build/generated/source/apt
目錄下看到 Dagger 2 編譯時生成的成員屬性注入類喇完。
public final class Man_MembersInjector implements MembersInjector<Man> {
private final Provider<Car> carProvider;
public Man_MembersInjector(Provider<Car> carProvider) {
assert carProvider != null;
this.carProvider = carProvider;
}
public static MembersInjector<Man> create(Provider<Car> carProvider) {
return new Man_MembersInjector(carProvider);
}
@Override
public void injectMembers(Man instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
instance.car = carProvider.get();
}
public static void injectCar(Man instance, Provider<Car> carProvider) {
instance.car = carProvider.get();
}
}
從上面的injectMembers
方法中可以看到注入依賴的代碼是instance.car = carProvider.get();
,所以@Inject
標注的成員屬性不能是private
的剥啤,不然無法注入锦溪。
創(chuàng)建所依賴對象的實例
用@Inject
標注構造函數時,Dagger 2 會完成實例的創(chuàng)建府怯。
public class Car {
@Inject
public Car() {}
}
build 后可以在build/generated/source/apt
目錄下看到 Dagger 2 編譯時生成的工廠類刻诊。
public final class Car_Factory implements Factory<Car> {
private static final Car_Factory INSTANCE = new Car_Factory();
@Override
public Car get() {
return new Car();
}
public static Factory<Car> create() {
return INSTANCE;
}
}
依賴注入是依賴的對象實例
-->需要注入的實例屬性
,上面完成兩步牺丙,通過 Dagger 2 生成的代碼代碼可以知道则涯,生成了 Man 的成員屬性注入類和 Car 的工廠類,接下來需要的就是新建工廠實例并調用成員屬性注入類完成 car 的實例注入冲簿。完成這個過程的橋梁就是dagger.Component
粟判。
Component 橋梁
@Component
可以標注接口或抽象類,Component 橋梁可以完成依賴注入過程峦剔,其中最重要的是定義注入接口档礁,調用注入接口就可以完成 Man 所需依賴的注入。
@Component
public interface ManComponent {
void injectMan(Man man); // 注入 man 所需要的依賴
}
build 后會生成帶有Dagger
前綴的實現該接口的類:DaggerManComponent
public final class DaggerManComponent implements ManComponent {
private MembersInjector<Man> ManMembersInjector;
private DaggerManComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static ManComponent create() {
return new Builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.ManMembersInjector = Man_MembersInjector.create(Car_Factory.create()); // 找到 Man 的成員屬性注入類吝沫,創(chuàng)建依賴的工廠
}
@Override
public void injectMan(Man man) {
ManMembersInjector.injectMembers(man); // 完成依賴注入
}
public static final class Builder {
private Builder() {}
public ManComponent build() {
return new DaggerManComponent(this);
}
}
}
從上面生成的代碼可以看出來 Component 就是連接依賴的對象實例
和需要注入的實例屬性
之間的橋梁呻澜。Component 會查找目標類對應的成員屬性注入類(即 MembersInjector<Man>),然后把依賴屬性的工廠實例(即 Car_Factory.create())傳給注入類野舶,再使用 Component 一開始定義的接口就能完成依賴注入易迹。注意,Component 中注入接口的參數必須為需要注入依賴的類型平道,不能是 Man 的父類或子類,注入接口返回值為 void供炼,接口名可以任意一屋。
接下來只需要在 Man 中調用injectMan
方法就能完成注入窘疮。
public class Man {
...
public Man() {
DaggerManComponent.create().injectMan(this);
}
...
}
Module
使用@Inject
標注構造函數來提供依賴的對象實例的方法,不是萬能的冀墨,在以下幾種場景中無法使用:
接口沒有構造函數
第三方庫的類不能被標注
構造函數中的參數必須配置
這時闸衫,就可以用@Provides
標注的方法來提供依賴實例,方法的返回值就是依賴的對象實例诽嘉,@Provides
方法必須在Module
中蔚出,Module 即用@Module
標注的類。所以 Module 是提供依賴的對象實例的另一種方式虫腋。
@Module
public class CarModule {
@Provides
static Car provideCar() {
return new Car();
}
}
約定俗成的是@Provides
方法一般以provide
為前綴骄酗,Moudle 類以Module
為后綴,一個 Module 類中可以有多個@Provides
方法悦冀。
接下來趋翻,需要把可以提供依賴實例的 Module 告訴 Component:
@Component(modules = CarModule.class)
public interface ManComponent {
void injectMan(Man man); // 注入 man 所需要的依賴
}
build之后,Module 和 Component 生成的類為:
public final class CarModule_ProvideCarFactory implements Factory<Car> {
private static final CarModule_ProvideCarFactory INSTANCE = new CarModule_ProvideCarFactory();
@Override
public Car get() {
return Preconditions.checkNotNull(
CarModule.provideCar(), "Cannot return null from a non-@Nullable @Provides method");
}
public static Factory<Car> create() {
return INSTANCE;
}
/** Proxies {@link CarModule#provideCar()}. */
public static Car proxyProvideCar() {
return CarModule.provideCar();
}
}
CarModule_ProvideCarFactory 和之前的 Car_Factory 類似盒蟆,都實現 Factory<Car> 接口踏烙。
生成的 DaggerManComponent 和之前相比只改變了一個方法:
private void initialize(final Builder builder) {
this.manMembersInjector = Man_MembersInjector.create(CarModule_ProvideCarFactory.create());
}
只是提供依賴實例的工廠變?yōu)榱?CarModule 對應的工廠。
總結
現在再來看 Dagger 2 最核心的三個部分:
需要注入依賴的目標類历等,需要注入的實例屬性由
@Inject
標注讨惩。提供依賴對象實例的工廠,用
@Inject
標注構造函數或定義Module
這兩種方式都能提供依賴實例寒屯,Dagger 2 的注解處理器會在編譯時生成相應的工廠類步脓。Module
的優(yōu)先級比@Inject
標注構造函數的高,意味著 Dagger 2 會先從 Module 尋找依賴實例浩螺。把依賴實例工廠創(chuàng)建的實例注入到目標類中的 Component靴患。
下面再講述上面提到的在 Dagger 2 種幾個注解的用法:
@Inject
一般情況下是標注成員屬性和構造函數,標注的成員屬性不能是private
要出,Dagger 2 還支持方法注入鸳君,@Inject
還可以標注方法。@Provides
只能標注方法患蹂,必須在 Module 中或颊。@Module
用來標注 Module 類@Component
只能標注接口或抽象類,聲明的注入接口的參數類型必須和目標類一致传于。
推薦閱讀:
想看更多精彩內容囱挑,歡迎關注我的公眾號 JohnnyShieh,每周一準時更新沼溜!