前言
??Dagger是幫助實現依賴注入的庫,雖然很多人都知道依賴注入對于架構設計的重要性,但是Dagger學習曲線十分陡峭,官方文檔更是看了幾遍也很難消化。本文旨在通過一篇文章來讓大家看懂并上手Dagger。
??Dagger最早由JakeWharton在square公司開發。后來轉由Google維護并發展為Dagger2。Dagger2區別于Dagger1的地方主要在于兩個,一個是由運行時通過反射構建依賴關系變為編譯期通過注解生成依賴關系,另一個是出錯時有更好地提醒(當然這也是因為Dagger2在編譯期間根據注解生成好了可讀性較好的代碼帶來的優勢)。 轉載請注明來源「Bug總柴」
參考
初學者建議先不要看官方文檔,可以先看這幾篇博客:
依賴注入
在學習Dagger之前,我們先來了解一下依賴注入。
什么是依賴注入
??依賴注入,顧名思義,就是說當代碼執行過程中需要某個服務對象的時候,不是通過當前代碼自己去構造或者去查找獲取服務對象,而是通過外部將這個服務對象傳給當前代碼。
??這樣做的好處在于當服務對象構建或者獲取方法改變時,不需要改變調用方的代碼,這也是S.O.L.I.D原則中開發封閉原則的具體表現。
如何實現依賴注入
在不使用Dagger等依賴注入庫的情況下,我們可以通過以下三種方式手動實現依賴注入。
- 構造器依賴注入
// Constructor
Client(Service service) {
// Save the reference to the passed-in service inside this client
this.service = service;
}
- Setter方法依賴注入
// Setter method
public void setService(Service service) {
// Save the reference to the passed-in service inside this client.
this.service = service;
}
- 接口依賴注入
// Service setter interface.
public interface ServiceSetter {
public void setService(Service service);
}
// Client class
public class Client implements ServiceSetter {
// Internal reference to the service used by this client.
private Service service;
// Set the service that this client is to use.
@Override
public void setService(Service service) {
this.service = service;
}
}
Dagger2基本概念
Dagger2可以理解成就是在編譯階段根據注解構建一個依賴關系圖,然后根據依賴關系圖之間的依賴關系生成對象工廠類,在需要的地方注入對象。如何使用注解構造一個依賴關系圖是Dagger2使用的關鍵。在了解注解之前,我們先來認識一下以下三個概念:
bindings
bindings的概念是告訴Dagger注入器如何能得到一個具體類。有幾種方法可以表示當前代碼可以提供某個類型的對象:
- 通過使用
@Provides
注解的非抽象方法返回一個類對象
@Provides
public Fruit providerApple() {
return new Apple();
}
- 通過
@Binds
注解的抽象方法,該抽象方法返回接口或抽象類,參數是一個該接口或者抽象類的具體實現類
@Binds
abstract Fruit bindApple(Apple apple);
- 通過
@Inject
注解的構造方法
public class Apple implements Fruit {
@Inject
public Apple() {
}
}
- 通過
multibindings
(@MapKey
后面提到)或者producers
(暫不細說)提供
modules
module是一個只有@Provides
和@Binds
方法的類,用于集合所有的依賴關系。同時module可以通過inculdes
來引入其他module從而得到其他module的依賴關系集合。例如:
@Module(includes = ProjectModule.class)
public abstract class FruitModule {
@Binds
abstract Fruit bindApple(Apple apple);
@Provides
static Desk provideDesk() {
return new Desk();
}
}
components
component
是被@Component
標注的接口或者抽象類,Dagger會負責實例化一個component
。component
中指定需要的modules
,代表著這次依賴構建所有需要的全部依賴關系都可以從modules
中找到。compoent
中的方法只能是無參的,且這個無參方法的返回值就是Dagger最終需要構建得到的實體。可以說構建component
中無參方法的返回值對象就是整個依賴關系查找的起源點。在構建這個實體時,如果遇到依賴,就會從modules
中不斷地傳遞查找,直到所有的依賴都被找到為止。如果中間有某些依賴沒有注明實例化方式,Dagger會在編譯期間報錯。具體component
的一個例子如下:
@Component(modules = {FruitModule.class, ProjectModule.class})
public interface FruitComponent {
FruitShop inject();
}
bindings\modules\components的依賴關系圖可以表示為下圖所示
Dagger2注解
知道上面的概念可以看懂基本的Dagger代碼。不過Dagger有非常多幫助完成依賴關系圖構建的注解,只有把這些注解都弄懂了,才能真正看懂Dagger2的代碼。下面兩個圖可以看到一些常用的注解:
下面我們來一一介紹一下。
@Inject
@Inject
是javax.inject
包中的注解,可以用于對類的構造函數、成員變量和方法。
用于類構造器中表示該類在依賴注入時使用被注解的構造器創建對象。
例如:
public class FruitShop {
@Inject
public FruitShop() {
}
}
表示當其他地方依賴于FruitShop
對象時,會使用FruitShop的默認構造方法進行創建。當被@Inject
注解的構造函數是有參數的,那么Dagger會同時對其參數進行注入。例如:
public class FruitShop {
@Inject
public FruitShop(Desk desk) {
}
}
當需要構建依賴關系時,在創建FruitShop的時候回對參數desk
進行注入。
在生成的FruitShop_Factory.java
代碼中,可以看到以下方法:
public final class FruitShop_Factory implements Factory<FruitShop> {
private final Provider<Desk> deskProvider;
public FruitShop_Factory(Provider<Desk> deskProvider) {
this.deskProvider = deskProvider;
}
public static FruitShop provideInstance(Provider<Desk> deskProvider) {
FruitShop instance = new FruitShop(deskProvider.get());
return instance;
}
}
@Inject
用于構造函數需要注意兩點:
- 每個類只允許一個構造方法注解為
@Inject
,例如
public class FruitShop {
// 由于有另外的構造函數注解了@Inject,這里不能再使用@Inject,否則編譯會出錯Error: Types may only contain one @Inject constructor.
public FruitShop() {
}
@Inject
public FruitShop(Location location) {
}
}
-
javax.inject.Inject
文檔中說明當被注解的構造函數是public且無參的默認構造函數@Inject
可以省略。但是實際Dagger2項目中,需要被注入的對象必須擁有@Inject
注解的構造方法或者通過@Porvides
注解的方法提供,否則會報錯Error: cannot be provided without an @Inject constructor or an @Provides-annotated method.
。這一點Dagger的處理與javax.inject.Inject
描述表現不一致。
用于成員變量中表示該成員變量作為依賴需要被注入
例如:
public class FruitShop {
@Inject
Fruit apple;
}
表示FruitShop
中需要依賴水果apple
,并希望由外部注入進來。
編譯之后我們會看到一個FruitShop_MembersInjector.java
的類,里面會有一個這樣的方法:
public final class FruitShop_MembersInjector implements MembersInjector<FruitShop> {
// Dagger生成代碼中會通過MembersInjector給我們對象需要的屬性進行注入
public static void injectApple(FruitShop instance, Fruit apple) {
instance.apple = apple;
}
}
對于屬性注解需要注意被注解的屬性不能是final
或者被private
修飾符修飾。其中的原因在上面instance.apple = apple;
代碼中不言而喻。
在生成的FruitShop_Factory.java
代碼中,可以看到以下方法:
public final class FruitShop_Factory implements Factory<FruitShop> {
private final Provider<Fruit> appleProvider;
public FruitShop_Factory(Provider<Fruit> appleProvider) {
this.appleProvider = appleProvider;
}
public static FruitShop provideInstance( Provider<Fruit> appleProvider) {
FruitShop instance = new FruitShop();
FruitShop_MembersInjector.injectApple(instance, appleProvider.get());
return instance;
}
}
用于方法中表示依賴于方法參數的類型會被注入
例如:
public class FruitShop {
Desk mDesk;
@Inject
public void setDesk(Desk desk) {
this.mDesk = desk;
}
}
被注解的setDesk()
方法有一個Desk
類型的參數,意味著需要對Desk
進行依賴注入。Dagger生成的代碼如下所示:
public final class FruitShop_Factory implements Factory<FruitShop> {
private final Provider<Desk> deskProvider;
public FruitShop_Factory(Provider<Desk> deskProvider) {
this.deskProvider = deskProvider;
}
public static FruitShop provideInstance(Provider<Desk> deskProvider) {
FruitShop instance = new FruitShop();
FruitShop_MembersInjector.injectSetDesk(instance, deskProvider.get());
return instance;
}
}
public final class FruitShop_MembersInjector implements MembersInjector<FruitShop> {
public static void injectSetDesk(FruitShop instance, Desk desk) {
instance.setDesk(desk);
}
}
@Inject
用于注解方法需要注意被注解的方法不能是private
的。被注解的方法支持擁有多個參數。如果標注在public方法上,Dagger2會在構造方法執行之后立即調用這個方法。
@Provides & @Module & @Component
使用@Inject
來標記依賴的注入不是什么時候都可以的,例如第三方api的代碼我們是不能修改的,沒辦法通過@Inject
注解第三方api類的構造器,從而沒辦法對第三方api的對象進行構建和依賴注入。這個時候我們可以使用@Provides
來提供對應的依賴。而@Provides
必須放到一個被@Module
注解的類中。例如:
// 通過在module中使用@Provides表示提供依賴的方法
@Module
public class FruitModule {
@Provides
Fruit provideApple() {
return new Apple();
}
}
// 使用@Inject說明需要依賴注入的地方
public class FruitShop {
// 這里需要提供一個Fruit類型的依賴
@Inject
Fruit apple;
@Inject
public FruitShop() {
}
}
// 將需要用到依賴的地方FruitShop和提供依賴的地方FruitModule綁定在一起
@Component(modules = FruitModule.class)
public interface FruitComponent {
FruitShop inject();
}
這里在module中聲明了一個可以提供Apple
類依賴的方法provideApple()
。并且component將依賴的需求方和提供方都綁定在了一起。我們來看生成的代碼
public final class FruitModule_ProvideAppleFactory implements Factory<Fruit> {
private final FruitModule module;
public FruitModule_ProvideAppleFactory(FruitModule module) {
this.module = module;
}
@Override
public Fruit get() {
return provideInstance(module);
}
public static Fruit provideInstance(FruitModule module) {
return proxyProvideApple(module);
}
public static FruitModule_ProvideAppleFactory create(FruitModule module) {
return new FruitModule_ProvideAppleFactory(module);
}
public static Fruit proxyProvideApple(FruitModule instance) {
return Preconditions.checkNotNull(
instance.provideApple(), "Cannot return null from a non-@Nullable @Provides method");
}
}
這段生成的代碼實際上是提供Apple
類工廠FruitModule_ProvideAppleFactory
,能夠通過provideApple()
提供Apple
對象。以下的代碼中,component通過傳遞FruitModule_ProvideAppleFactory
對象到FruitShop_Factory
中完成對FruitShop
的依賴注入
public final class DaggerFruitComponent implements FruitComponent {
private FruitModule_ProvideAppleFactory provideAppleProvider;
private void initialize(final Builder builder) {
this.provideAppleProvider = FruitModule_ProvideAppleFactory.create(builder.fruitModule);
this.fruitShopProvider = DoubleCheck.provider(FruitShop_Factory.create(provideAppleProvider));
}
}
通過@Provides
@Module
@Component
三個注解就可以完成最基本的依賴注入關系圖的構造,從而使用Dagger給依賴進行注入。這里需要注意:
- 通過
@Provides
注解的方法不能返回null,否則會報NullPointerException
。如果@Provides
方法可能返回null,那需要加上注入@Nullable
,同時在需要依賴注入的地方加上@Nullable
標注。 - 一般module類都使用XXXModule命名,而provide方法一般都使用provideXXX命名方式。
@Binds
@Binds
的作用和@Provides
的作用是一樣的,是提供接口依賴的一種簡潔表示的方式。例如下面這個例子:
@Module
public class FruitModule {
@Provides
Fruit provideApple() {
return new Apple();
}
}
使用@Binds
可以簡化為:
@Module
abstract public class FruitModule {
@Binds
abstract Fruit bindApple(Apple apple);
}
表示當需要依賴Furit
接口時,使用Apple
實例對象進行注入。需要注意的是,使用@Binds
標注的方法必須有且僅有一個方法參數,且這個方法參數是方法返回值的實現類或者子類。
@Component
因為Componet較為復雜,拿出來再單獨說一下。Component的聲明如下:
public @interface Component {
Class<?>[] modules() default {};
Class<?>[] dependencies() default {};
@interface Builder {}
}
這代表著@Component
的標簽中除了可以指定modules之外還可以通過dependencies引用其他的component。在被@Component
注解的類必須是接口或者抽象類,這個被注解的類中可以包含以下三個東西:
- 表示需要提供的依賴的方法,例如:
// 表示需要注入依賴生成SomeType類對象
SomeType getSomeType();
// 表示需要注入依賴生成Set<SomeType>對象,multibinding后面會介紹
Set<SomeType> getSomeTypes();
// 表示需要注入生成一個Qualifier為PortNumber的int整形,Qualifier后面會介紹
@PortNumber int getPortNumber();
// 表示需要注入依賴生成Provider<SomeType>對象,Provider<>后面介紹
Provider<SomeType> getSomeTypeProvider();
// 表示需要注入依賴生成Lazy<SomeType>對象,Lazy<>后面會介紹
Lazy<SomeType> getLazySomeType();
- 表示需要注入成員依賴的方法,
// 表示需要將someType中標記為依賴的屬性和方法進行注入
void injectSomeType(SomeType someType);
// 表示需要將someType中標記為依賴的屬性和方法進行注入,并返回SomeType對象
SomeType injectAndReturnSomeType(SomeType someType);
- 構造Component的Builder
Dagger生成Component實現類時,會自動根據Bulder模式生成所需要Builder類。當Component所依賴的Module為非抽象且默認構造函數為private時,則Dagger會生成對應的有傳入module方法的Builder類,例如:
@Component(modules = ProjectModule.class)
public interface FruitComponent {
FruitShop inject();
}
@Module
public class ProjectModule {
private Desk mDesk;
private ProjectModule(){}
public ProjectModule(Desk desk){
mDesk = desk;
}
@Provides
public Desk provide() {
return mDesk;
}
}
則在生成的DaggerFruitComponent中會有以下Builder方法
public final class DaggerFruitComponent implements FruitComponent {
private ProjectModule projectModule;
public static final class Builder {
private ProjectModule projectModule;
private Builder() {}
public FruitComponent build() {
if (projectModule == null) {
throw new IllegalStateException(ProjectModule.class.getCanonicalName() + " must be set");
}
return new DaggerFruitComponent(this);
}
public Builder projectModule(ProjectModule projectModule) {
this.projectModule = Preconditions.checkNotNull(projectModule);
return this;
}
}
}
在調用時需要傳入依賴的module:
FruitShop fruitShop = DaggerFruitComponent
.builder()
.projectModule(new ProjectModule(new Desk()))
.build()
.inject();
當Component所依賴的module和其他Componet都不需要使用有參的構造函數的話,Component可以使用簡潔的create()
方法,例如將上面的module改為:
@Module
public class ProjectModule {
@Provides
public Desk provide() {
return new Desk();
}
}
則生成的componet會是這樣的:
public final class DaggerFruitComponent implements FruitComponent {
public static FruitComponent create() {
return new Builder().build();
}
public static final class Builder {
private ProjectModule projectModule;
private Builder() {}
public FruitComponent build() {
if (projectModule == null) {
this.projectModule = new ProjectModule();
}
return new DaggerFruitComponent(this);
}
public Builder projectModule(ProjectModule projectModule) {
this.projectModule = Preconditions.checkNotNull(projectModule);
return this;
}
}
}
在調用時僅需調用create()
方法既可
FruitShop fruitShop = DaggerFruitComponent.create().inject();
@Qualifier
在上面了解完@Inject
之后,大家可能有個疑惑,使用@Inject
注入的對象如果是接口或者抽象類怎么辦呢?在不同的地方可能需要不同的接口或者抽象類的實現,怎么讓Dagger知道我究竟需要的哪種實現類呢?例如:
public class FruitShop {
@Inject
Fruit apple;
@Inject
Fruit orange;
}
??這里代碼需要對apple
和orange
進行注入,但是對于Fruit
的注入只能聲明一個,所以這個地方apple
和orange
要么都被注入成class Apple implements Fruit
或者class Orange implements Fruit
。
??@Qualifier
這個時候就能作為一個限定符派上用場了。@Qualifier
是加在注解之上的注解(也稱為元注解),當需要注入的是接口或者抽象類,就可以使用@Qualifier
來定義一個新的注解用來表明對應需要的依賴關系。使用@Qualifier
可以實現指定apple
需要用Apple
注入,orange
需要使用Orange
類注入。例如我們可以這樣實現
// 首先定義一個表示水果類型的注解
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface FruitType {
String value() default "";
}
// 接著使用這個注解表示對應依賴關系
@Module
public abstract class FruitModule {
@Binds @FruitType("apple")
abstract Fruit bindApple(Apple apple);
@Binds @FruitType("orange")
abstract Fruit bindOrange(Orange orange);
}
// 使用時標記相應的注解既可
public class FruitShop {
@Inject @FruitType("apple")
Fruit apple;
@Inject @FruitType("orange")
Fruit orange;
}
除了可以聲明value為String的注解外,還可以傳入其他類型,例如我們需要一張顏色是紅色的桌子:
@java.lang.annotation.Documented
@java.lang.annotation.Retention(RUNTIME)
@javax.inject.Qualifier
public @interface DeskColor {
Color color() default Color.RED;
public enum Color { RED, WHITE }
}
在Module中可以指定具體生成的對象:
@Module
public class ProjectModule {
@Provides @DeskColor(color = DeskColor.Color.RED)
public Desk provideDesk() {
return new Desk("RED");
}
}
在使用時再進行標記既可:
public class FruitShop {
@Inject
public FruitShop() {
}
@Inject @DeskColor(color = DeskColor.Color.RED)
Desk desk;
}
通過@Qualifier
定義注解可以實現對同一個接口或抽象類的指定不同對象注入。
@Named
了解完@Qualifier
之后再看看@Name
的聲明:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
可以看出除了接口名稱不一樣之外,其余的和上面定義的@FruitType
是一致的,所以其實@Named
只是系統定義好的,參數為String的默認限定符。將上面代碼中的@FruitType
改成@Named
能達到一樣的效果。
@Scope和@Singleton
@Scope
是另一個元注解,它的作用是告訴注入器要注意對象的重用的生命周期。其中@Scope
的聲明如下:
@Target(ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
public @interface Scope {}
我們再看@Singleton
的聲明:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
可以發現@Singleton
就是被@Scope
聲明的注解,可以作為一個生命周期的注解符。
例如我們需要注入一個Desk
類,我們希望一個FruitShop
對應只有一個Desk
,正常的情況下我們是這樣聲明的:
public class FruitShop {
@Inject
public FruitShop() {
}
@Injec
Desk desk;
@Inject
Desk desk2;
public String checkDesk() {
return desk == desk2 ? "desk equal" : "desk not equal";
}
}
@Module
public class ProjectModule {
@Provides
public Desk provideDesk() {
return new Desk();
}
}
@Component(modules = ProjectModule.class)
public interface FruitComponent {
FruitShop inject();
}
在Main函數中執行
public class Main {
public static void main(String[] args) {
FruitShop fruitShop = DaggerFruitComponent.create().inject();
System.out.println(fruitShop.checkDesk());
}
}
得到的結果是
desk not equal
Process finished with exit code 0
當然這不是我們希望得到的結果,下面我們來用@Singleton
改造一下如下:
public class FruitShop {
@Inject
public FruitShop() {
}
@Injec
Desk desk;
@Inject
Desk desk2;
public String checkDesk() {
return desk == desk2 ? "desk equal" : "desk not equal";
}
}
@Module
public class ProjectModule {
@Provides
@Singleton
public Desk provideDesk() {
return new Desk();
}
}
@Singleton
@Component(modules = ProjectModule.class)
public interface FruitComponent {
FruitShop inject();
}
現在Rebuild之后再運行一下:
desk equal
Process finished with exit code 0
和之前不同的地方在于我們隊Component和module中的provide方法都加了@Singleton
標記。我們來看看對比下前后生成的代碼有什么區別:
可以看出,兩次生成的代碼中,只有DaggerFruitComponent
有區別,其中的區別在于在Component中provideDeskProvider
在有@Singleton
標注的例子中是單例的存在:
private void initialize(final Builder builder) {
// DoubleCheck.provider就是用了雙重檢驗的單例模式提供單例
this.provideDeskProvider =
DoubleCheck.provider(ProjectModule_ProvideDeskFactory.create(builder.projectModule));
}
private FruitShop injectFruitShop(FruitShop instance) {
FruitShop_MembersInjector.injectDesk(instance, provideDeskProvider.get());
FruitShop_MembersInjector.injectDesk2(instance, provideDeskProvider.get());
return instance;
}
其中DoubleCheck.get()
方法使用雙重判斷獲取單例。
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
instance = reentrantCheck(instance, result);
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
}
}
而在沒有使用@Singletion
的例子中,并沒有使用單例來提供Desk
對象:
private void initialize(final Builder builder) {
this.projectModule = builder.projectModule;
}
private FruitShop injectFruitShop(FruitShop instance) {
FruitShop_MembersInjector.injectDesk(
instance, ProjectModule_ProvideDeskFactory.proxyProvideDesk(projectModule));
FruitShop_MembersInjector.injectDesk2(
instance, ProjectModule_ProvideDeskFactory.proxyProvideDesk(projectModule));
return instance;
}
通過上面的例子可以看出@Scope
是用來定義需要的依賴對象在一個Component依賴關系圖生成中是否需要重用,且重用的范圍在一個Component對象的引用范圍內。至于@Scope
的意義在于可以在Components之間的依賴中使得依賴對象在不同的Components中重用。Components之間的依賴會在后面介紹。
這里需要注意幾點:
-
@Scope
注解的注解不能用于標注依賴的構造函數 - 沒有被
@Scope
注解的注解(如@Singleton
)注解的componet不能存在被@Scope
注解的注解(如@Singleton
)注解的方法(有點繞,可以理解成沒有標志為@Singleton
的componet不能擁有標志為@Singleton
的方法) - 如果componet定義了一個scope,那么這個componet里面只能存在沒有scoped的依賴關系,或者擁有跟componet一樣scope的依賴關系
- 使用Componet的調用方需要負責重用范圍的定義,例如希望有一個全局的單例,那么則需要保存一個擁有全局生命周期的component依賴生成類對象。
@Reusable
與@Singleton
類似的,@Reusable
也是被@Scope
注釋的注釋。與@Singleton
不同的是,@Reusable
只表示Dagger生成的對象可以被緩存起來,從而節省內存消耗,但是不能保證對象的單例性質。我們將上面例子中的@Singleton
改成@Reusable
@Module
public class ProjectModule {
@Provides
@Reusable
public Desk provideDesk() {
return new Desk();
}
}
rebuild之后我們來看生成的代碼
public final class DaggerFruitComponent implements FruitComponent {
private void initialize(final Builder builder) {
this.provideDeskProvider =
SingleCheck.provider(ProjectModule_ProvideDeskFactory.create(builder.projectModule));
}
private FruitShop injectFruitShop(FruitShop instance) {
FruitShop_MembersInjector.injectDesk(instance, provideDeskProvider.get());
FruitShop_MembersInjector.injectDesk2(instance, provideDeskProvider.get());
return instance;
}
}
其中SingleCheck.get()
方法如下:
public final class SingleCheck<T> implements Provider<T> {
@Override
public T get() {
Object local = instance;
if (local == UNINITIALIZED) {
// provider is volatile and might become null after the check, so retrieve the provider first
Provider<T> providerReference = provider;
if (providerReference == null) {
// The provider was null, so the instance must already be set
local = instance;
} else {
local = providerReference.get();
instance = local;
// Null out the reference to the provider. We are never going to need it again, so we can
// make it eligible for GC.
provider = null;
}
}
return (T) local;
}
}
與剛剛的區別是由DoubleCheck.provider
變成了SingleCheck.provider
,從代碼實現可以看出@Reusable
并不是嚴格的單例模式,只是對對象進行了緩存。
@Component的dependencies和@SubComponent
雖然獨立的沒有scope范圍的component已經非常實用了,但是在某些情況可能需要用到多個不同scope的不同componet。不同的Component之間可以通過指定依賴關系來聯系起來。Components之間的關聯可以采取兩種方式:指定指定dependencies或者SubComponet。下面我們來看看二者的區別。
-指定dependencies
當一個Component需要從另一個Componet中獲得依賴的時候,可以使用@Component(dependencies = {XXXComponent.class})
來引用其他component的依賴。需要注意的是,被引用的Component需要顯示暴露出給外部的依賴,不然編譯會報錯。看下面這個例子。
@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
FruitShop inject();
// 對外暴露的依賴,表示其他Component可以從這個Component中
// 獲得@FruitType為apple的類型為Fruit的依賴
@FruitType("apple")
Fruit getApple();
}
在有了水果的依賴之后,我們創建一個果汁的依賴關系:
public interface Juice {
String name();
}
public class AppleJuice implements Juice {
private Fruit mApple;
// 這里要構建一個蘋果汁,需要用到蘋果,這個依賴需要從FruitComponent中獲得
@Inject
public AppleJuice(@FruitType("apple") Fruit apple) {
mApple = apple;
}
@Override
public String name() {
return mApple.name();
}
}
@Module
abstract public class JuiceModule {
@OtherScop
@Binds @JuiceType("appleJuice")
abstract Juice bindAppleJuice(AppleJuice appleJuice);
}
// 這里通過指定dependencies,指出JuiceComponent需要FruitComponent作為依賴
@OtherScop
@Component(dependencies = {FruitComponent.class}, modules = {JuiceModule.class})
public interface JuiceComponent {
JuiceShop inject();
}
public class JuiceShop {
@Inject
public JuiceShop(){}
// 構建果汁商店需要一個蘋果汁,Dagger將負責構建
@Inject
@JuiceType("appleJuice")
public Juice appleJuice;
public String getJuice() {
return appleJuice.name();
}
}
上面的代碼,關注JuiceComponent
類,這個類本身依賴關系圖從JuiceModule
中獲得,而JuiceModule
類只是聲明了JuiceType
為appleJuice
的Juice
類通過創建AppleJuice
獲得。而觀察AppleJuice
類需要一個FruitType
為apple
的Fruit
類作為依賴。這個Fruit
類的依賴并不能從JuiceComponent
中獲得,因此我們指定擁有這個Fruit
類依賴的dependencies = {FruitComponent.class}
。在FruitComponent
類中需要顯示聲明其可以提供FruitType
為apple
的Fruit
類如下:
@FruitType("apple")
Fruit getApple();
因此通過指定dependencies = {FruitComponent.class}
構成了完整的依賴關系鏈,我們可以如下構建一個JuiceShop
:
public static void main(String[] args) {
JuiceShop juiceShop = DaggerJuiceComponent
.builder()
.fruitComponent(DaggerFruitComponent.create())
.build()
.inject();
System.out.println(juiceShop.getJuice());
}
Dagger會給我們生成DaggerJuiceComponent
,并通過fruitComponent()
方法,放入DaggerFruitComponent
的依賴。我們來看看Dagger生成的DaggerJuiceComponent
具體是如何使用DaggerFruitComponent
來生成依賴的:
public final class DaggerJuiceComponent implements JuiceComponent {
private com_shen_example_di_FruitComponent_getApple getAppleProvider;
private DaggerJuiceComponent(Builder builder) {
initialize(builder);
}
private void initialize(final Builder builder) {
// 保存fruitComponent到com_shen_example_di_FruitComponent_getApple內部類中
this.getAppleProvider = new com_shen_example_di_FruitComponent_getApple(builder.fruitComponent);
// 將保存有fruitComponent的內部類傳遞給AppleJuice構造工廠
this.appleJuiceProvider = AppleJuice_Factory.create(getAppleProvider);
}
private static class com_shen_example_di_FruitComponent_getApple implements Provider<Fruit> {
private final FruitComponent fruitComponent;
com_shen_example_di_FruitComponent_getApple(FruitComponent fruitComponent) {
this.fruitComponent = fruitComponent;
}
// 通過fruitComponent創建apple
@Override
public Fruit get() {
return Preconditions.checkNotNull(
fruitComponent.getApple(), "Cannot return null from a non-@Nullable component method");
}
}
public static final class Builder {
private FruitComponent fruitComponent;
public JuiceComponent build() {
return new DaggerJuiceComponent(this);
}
public Builder fruitComponent(FruitComponent fruitComponent) {
this.fruitComponent = Preconditions.checkNotNull(fruitComponent);
return this;
}
}
}
public final class AppleJuice_Factory implements Factory<AppleJuice> {
private final Provider<Fruit> appleProvider;
public AppleJuice_Factory(Provider<Fruit> appleProvider) {
this.appleProvider = appleProvider;
}
@Override
public AppleJuice get() {
return provideInstance(appleProvider);
}
public static AppleJuice provideInstance(Provider<Fruit> appleProvider) {
// 最終通過調用保存有fruitComponent的get方法,
// 通過fruitComponent創建apple,并傳入給AppleJuice構造函數中
return new AppleJuice(appleProvider.get());
}
public static AppleJuice_Factory create(Provider<Fruit> appleProvider) {
return new AppleJuice_Factory(appleProvider);
}
}
通過上述Dagger生成的代碼可以看出,通過dependencies方式指定Component依賴,Dagger會將依賴的Component通過組合方式傳入給目標的Component,并在目標Component需要創建依賴時,通過組合傳入的依賴Component進行依賴類的構建。再次強調,如果沒有在依賴Component中聲明其對外暴露的依賴,會出現報錯。例如假設我們將上面的FruitComponent
去掉getApple
方法:
@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
FruitShop inject();
}
那么在編譯時會出現報錯:
Error:(8, 8) java: [Dagger/MissingBinding] @com.shen.example.di.FruitType("apple") com.shen.example.fruit.Fruit cannot be provided without an @Provides-annotated method.
@com.shen.example.di.FruitType("apple") com.shen.example.fruit.Fruit is injected at
com.shen.example.juice.AppleJuice(apple)
com.shen.example.juice.AppleJuice is injected at
com.shen.example.di.JuiceModule.bindAppleJuice(appleJuice)
@com.shen.example.di.JuiceType("appleJuice") com.shen.example.juice.Juice is injected at
com.shen.example.JuiceShop.appleJuice
com.shen.example.JuiceShop is provided at
com.shen.example.di.JuiceComponent.inject()
-@SubComponent
@SubComponent
聲明的接口或者抽象類,表示其本身的依賴關系圖是不完整的,必須通過依附于外部的Component才能獲得完整的依賴關系。使用@SubComponent
有兩種方式,第一種是通過在被依賴的Component中聲明返回SubComponent類型的方法,并使用SubComponent中聲明的需要傳入參數的Module作為參數。第二種是在Component聲明的Module中,通過Module.subcomponents指定這個Module可以為哪些SubComponent提供依賴來源。
我們先看第一種方法,對比使用dependencies方式,只需要改變以下兩個類:
@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
FruitShop inject();
// 通過在被依賴的Component中聲明返回SubComponent類型的方法,
// 并使用SubComponent中聲明的需要傳入參數的Module作為參數。
// 由于JuiceComponent沒有指定有參的Module,因此這里方法的參數可以為空
JuiceComponent juiceComponent();
}
// 將JuiceComponent標注為Subcomponent,去掉dependencies指定
@OtherScop
@Subcomponent(modules = {JuiceModule.class})
public interface JuiceComponent {
JuiceShop inject();
}
我們可以如下構建一個JuiceShop
:
public static void main(String[] args) {
JuiceShop juiceShop = DaggerFruitComponent
.builder()
.build().juiceComponent()
.inject();
System.out.println(juiceShop.getJuice());
}
我們來看生成的DaggerFruitComponent
public final class DaggerFruitComponent implements FruitComponent {
// 通過FruitComponent中轉至JuiceComponent
@Override
public JuiceComponent juiceComponent() {
return new JuiceComponentImpl();
}
private final class JuiceComponentImpl implements JuiceComponent {
private AppleJuice_Factory appleJuiceProvider;
private Provider<Juice> bindAppleJuiceProvider;
private JuiceComponentImpl() {
initialize();
}
@SuppressWarnings("unchecked")
private void initialize() {
this.appleJuiceProvider = AppleJuice_Factory.create((Provider) Apple_Factory.create());
this.bindAppleJuiceProvider = DoubleCheck.provider((Provider) appleJuiceProvider);
}
// 最終會通過中轉得到的JuiceComponent,調用inject方法得到目標對象
@Override
public JuiceShop inject() {
return injectJuiceShop(JuiceShop_Factory.newJuiceShop());
}
@CanIgnoreReturnValue
private JuiceShop injectJuiceShop(JuiceShop instance) {
JuiceShop_MembersInjector.injectAppleJuice(instance, bindAppleJuiceProvider.get());
return instance;
}
}
}
可以看出,當使用@SubModule時,JuiceComponent
被聲明為FruitComponent
的內部類,通過內部中轉至JuiceComponent
從而構造出目標對象。
第二種使用@Module.subcomponents,相比第一種SubComponent方法而言,不需要在在被依賴的Component中聲明返回SubComponent類型的方法,只需要在被依賴的Component對應的Module中聲明subcomponent既可。同時對SubComponent要求有@Subcomponent.Builder。
我們看FruitComponent不在需要聲明返回JuiceComponent的方法
@Singleton
@Component(modules = {FruitModule.class})
public interface FruitComponent {
FruitShop inject();
// 不需要額外聲明SubComponent
//JuiceComponent juiceComponent();
}
@OtherScop
@Subcomponent(modules = {JuiceModule.class})
public interface JuiceComponent {
JuiceShop inject();
// 需要添加SubComponent.Builder
@Subcomponent.Builder
interface Builder {
JuiceComponent build();
}
}
同時對于JuiceComponent需要依賴的module添加subComponent依賴
// 對Module加入subcomponents = {JuiceComponent.class}
@Module(subcomponents = {JuiceComponent.class})
abstract public class FruitModule {
@Binds @FruitType("apple")
abstract Fruit bindApple(Apple apple);
@Binds @FruitType("orange")
abstract Fruit bindOrange(Orange orange);
}
這個時候可以在被依賴的Component生成產物的FruitShop中構造出JuiceShop
public class FruitShop {
// 這里可以直接使用JuiceComponent.Builder,Provider的作用后面再說
@Inject
public Provider<JuiceComponent.Builder> juiceComponentProvider;
@Inject
public FruitShop() {}
public String juice() {
// 通過聲明需要注入一個JuiceComponent,從而獲得JuiceShop
JuiceShop juiceShop = juiceComponentProvider.get().build().inject();
return juiceShop.getJuice();
}
}
從生成的DaggerFruitComponent來看
public final class DaggerFruitComponent implements FruitComponent {
private Provider<JuiceComponent.Builder> juiceComponentBuilderProvider;
// 初始化juiceComponentBuilderProvider
private void initialize(final Builder builder) {
this.juiceComponentBuilderProvider =
new Provider<JuiceComponent.Builder>() {
@Override
public JuiceComponent.Builder get() {
return new JuiceComponentBuilder();
}
};
}
// 將juiceComponentBuilderProvider注入到FruitShop中
private FruitShop injectFruitShop(FruitShop instance) {
FruitShop_MembersInjector.injectJuiceComponentProvider(instance, juiceComponentBuilderProvider);
return instance;
}
private final class JuiceComponentBuilder implements JuiceComponent.Builder {
// 通過JuiceComponent.Builder生成JuiceComponentImpl,這就是為什么通過@Module.subcomponents一定要聲明Builder的原因。
@Override
public JuiceComponent build() {
return new JuiceComponentImpl(this);
}
}
// JuiceComponentImpl與第一種的SubComponent方法內容類似,省略
private final class JuiceComponentImpl implements JuiceComponent {
// ……
}
}
-指定dependencies與SubComponent區別
- dependencies可以同時指定多個,而采用SubComponent只能有一個parent Component
- dependencies指定的Component與本身的Component是屬于組合關系,他們各自獨立,可以單獨使用。而SubComponent必須依賴于某個Component,Dagger不會對SubComponent生成DaggerXXXSubComponent類,而是在DaggerXXXComponent中定義了SubComponentImpl的內部類。
- 調用生成對象的時候依賴方向不同。使用dependencies方式,需要外部依賴的Componet和被依賴的Componet之間相互獨立,會生成兩個DaggerXXXComponet,并且是通過需要依賴的Componet發起,通過引入外部的Component來構建出最終的對象;而通過
@SubComponent
方式則是只生成一個DaggerXXXComponent,由被依賴的Component發起,通過中轉至需要其依賴的內部Component或者從依賴的Component生成對象內部來構建出最終對象。見如下代碼:
// dependencies方式
JuiceShop juiceShop = DaggerJuiceComponent
.builder()
.fruitComponent(DaggerFruitComponent.create())
.build()
.inject();
// @SubComponent第一種方式
JuiceShop juiceShop = DaggerFruitComponent
.builder()
.build().juiceComponent()
.inject();
// @SubComponent第二種方式
public class FruitShop {
@Inject
public Provider<JuiceComponent.Builder> juiceComponentProvider;
@Inject
public FruitShop() {}
public void createJuiceShop() {
JuiceShop juiceShop = juiceComponentProvider.get().build().inject();
}
}
??使用SubComponent的有兩個好處,第一個是可以對不同的component聲明不同的生命周期,規范對象存活的周期。第二個是為了更好的封裝,將相同的依賴放置到同一個component并依賴于它,而將不同的依賴封裝到不同的模塊。
??對于Component的依賴關系介紹到這里。在平常的使用中,如果module之間依賴較多的話,不建議采用@SubComponent第一種方式,因為這種方式每增加一個submodule都要在被依賴的component中聲明。如果被依賴的component比較穩定,建議使用dependencies方式,這樣新增加一個依賴的component不用修改被依賴的component。而@SubComponent第二種方式僅適用于依賴的component是作為被依賴component的一個附屬情況下使用,因為subcomponent無法脫離被依賴component的構建產物使用。不過第二種@SubComponent方式相對第一種方式而言,會讓Dagger知道SubComponent是否被使用,從而減少生成沒有被使用的SubComponent的代碼。
Lazy<> & Provider<>
依賴注入有三種模式,一種是最常見的直接注入(Direct Injection),還有就是懶注入(Lazy Injection)和提供者注入(Provider Injection)。直接注入模式下,被注入的對象會先生成,然后當有需要被注入的地方時,將預先生成的對象賦值到需要的地方。Lazy注入只有當get的時候才會創建對象,且生成之后對象會被緩存下來。Provider注入在每次get都會創建新的對象。
用官方的一個例子來說明。
@Module
public class CounterModule {
private int next = 100;
@Provides
Integer provideInteger() {
System.out.println("computing...");
return next++;
}
}
CounterModule
可以提供一個整形變量,每次提供完之后會對這個變量加一。
/**
* 直接注入
*/
public class DirectCounter {
@Inject
Integer value;
void print() {
System.out.println("direct counter printing...");
System.out.println(value);
System.out.println(value);
System.out.println(value);
}
}
/**
* Provider注入
*/
public class ProviderCounter {
@Inject
Provider<Integer> provider;
void print() {
System.out.println("provider counter printing...");
System.out.println(provider.get());
System.out.println(provider.get());
System.out.println(provider.get());
}
}
/**
* Lazy注入
*/
public class LazyCounter {
@Inject
Lazy<Integer> lazy;
void print() {
System.out.println("lazy counter printing...");
System.out.println(lazy.get());
System.out.println(lazy.get());
System.out.println(lazy.get());
}
}
/**
* 多個Lazy注入,lazy與單例
*/
public class LazyCounters {
@Inject
LazyCounter counter1;
@Inject
LazyCounter counter2;
void print() {
System.out.println("lazy counters printing...");
counter1.print();
counter2.print();
}
}
我們將這幾種的Counter集合到一起并輸入
public class Counter {
@Inject
DirectCounter mDirectCounter;
@Inject
ProviderCounter mProviderCounter;
@Inject
LazyCounter mLazyCounter;
@Inject
LazyCounters mLazyCounters;
public void print() {
mDirectCounter.print();
mProviderCounter.print();
mLazyCounter.print();
mLazyCounters.print();
}
}
得到以下的輸入結果:
// 直接注入
computing...
direct counter printing...
100
100
100
// Provider注入
provider counter printing...
computing...
101
computing...
102
computing...
103
// Lazy注入
lazy counter printing...
computing...
104
104
104
// 多個Lazy注入
lazy counters printing...
lazy counter printing...
computing...
105
105
105
lazy counter printing...
computing...
106
106
106
從結果可以看出,直接注入會先計算一次得到需要被注入的依賴對象(這里是整型100),并在需要的地方都返回這個預先計算好的對象,因此都返回100。
Provider注入則會在每次get方法調用的地方都通過Module中的provider方法計算得到需要被注入的依賴對象,因此依次返回新計算的對象101、102、103。
Lazy注入與直接注入相似,只會計算一次需要被注入的依賴對象,但是與直接注入不同的是,Lazy注入只有在被調用get方法的時候才會進行計算,因此可以看到lazy counter printing...
先打印,然后才是computing...
。
需要注意的是Lazy注入并不等同于單例模式,不同的LazyCounter
的get方法會獲取到不同的對象。例如LazyCounters
中通過兩個LazyCounter
的get方法分別獲取到的是105和106,并且lazy counter printing...
和computing...
都打印了兩次。
@BindsInstance
當構建Component的時候,如果需要外部傳入參數,我們有兩種方法,一種是通過構建Module時通過Module的構造函數傳入參數,第二種是通過@BindsInstance
方式,在構建Component的時候通過Component.Builder來構建Component。我們先看第一種方法:
// Pear對象需要一個String類型的名稱
public class Pear implements Fruit {
String customName;
public Pear(String name) {
customName = name;
}
@Override
public String name() {
if (customName != null && customName.length() > 0) {
return customName;
} else {
return "pear";
}
}
}
// ProjectModule的構造函數接收一個String類型的參數,并最終用于構造Pear對象
@Module
public class ProjectModule {
String name;
public ProjectModule(String name) {
this.name = name;
}
@Provides @Name
public String provideName() {
return name;
}
@Provides @FruitType("pear")
public Fruit providerPear(@Nullable @Name String name) {
return new Pear(name);
}
}
// Component不需要特別的處理
@Singleton
@Component(modules = {ProjectModule.class})
public interface FruitComponent {
FruitShop inject();
}
public class FruitShop {
@Inject @FruitType("pear")
Fruit pear;
// 打印出Pear的名字
public String createFruit() {
return pear.get().name();
}
}
public class Main {
public static void main(String[] args) {
// 通過projectModule構造傳遞參數
FruitShop fruitShop = DaggerFruitComponent
.builder()
.projectModule(new ProjectModule("cus_Pear"))
.build()
.inject();
System.out.println(fruitShop.createFruit());
}
}
上面代碼中,通過ProjectModule
的構建函數傳入了一個String對象參數,并最后用于構造Pear
對象,最終會打印cus_Pear
。對于這種在依賴關系圖中需要外部傳入參數的情況,可以使用@BindInstance
來進行優化。優化之后的代碼如下:
// ProjectModule中不需要另外聲明構造函數
@Module
public class ProjectModule {
@Provides @FruitType("pear")
public Fruit providerPear(@Name String name) {
return new Pear(name);
}
}
@Component(modules = {ProjectModule.class})
public interface FruitComponent {
FruitShop inject();
// 通過Component.Builder并使用BindsInstance提供依賴需要參數
@Component.Builder
interface Builder {
@BindsInstance
Builder cusPearName(@Name String name);
FruitComponent build();
}
}
public class Main {
public static void main(String[] args) {
// 使用builder中的cusPearName方法傳入參數
FruitShop fruitShop = DaggerFruitComponent
.builder()
.cusPearName("cus_Pear")
.build()
.inject();
System.out.println(fruitShop.createFruit());
}
}
與第一種方法不同,這種方法并不需要使用Module的帶參數構造方法來傳遞依賴所需的參數,而是通過Component構造時候在build的過程中通過cusPearName
方法傳入依賴對象,邏輯更加清晰,并且減少了Module的復雜度。
使用@BindInstance
注解的方法,如果參數沒有標記為@Nullable
則這個方法必須要調用,否則會報java.lang.IllegalStateException: java.lang.String must be set
。傳入參數必須為非null,否則會報java.lang.NullPointerException at dagger.internal.Preconditions.checkNotNull(Preconditions.java:33)
。如果這個參數是可選,則必須聲明為nullable,如下:
@Module
public class ProjectModule {
@Provides @FruitType("pear")
public Fruit providerPear(@Nullable @Name String name) {
return new Pear(name);
}
}
@Component(modules = {ProjectModule.class})
public interface FruitComponent {
FruitShop inject();
@Component.Builder
interface Builder {
@BindsInstance
Builder cusPearName(@Nullable @Name String name);
FruitComponent build();
}
}
在實際項目中,應該盡量使用@BindInstance
,而不是帶參數構造函數的module。
@BindsOptionalOf
@MapKey
@Multibinds
@IntoMap @IntoSet @ElementsIntoSet
@StringKey @IntKey @LongKey @ClassKey
Dagger2的缺點
- 修改完相關依賴之后必須Rebuild才能生效
- 代碼檢索變得相對困難,對于接口或者抽象類沒辦法直觀看到具體生成的是哪個對象
Kodein - 編寫Dagger代碼時需要關注比較多的規則約束,且不太容易記憶(例如Component中的方法要求,以及Builder里的方法要求等)
最后
Dagger2是非常棒的依賴注入器,但是Dagger2使用存在上述的一些缺點,所以建議僅在如架構關系之類的關鍵且依賴關系相對不經常修改的地方使用,不建議在項目中大范圍使用。
例子代碼下載:
本文的代碼可以在github中下載:https://github.com/shenguojun/DaggerExample