前语
Dagger是协助完结依靠注入的库,虽然很多人都知道依靠注入关于架构设计的重要性,可是Dagger学习曲线十分峻峭,官方文档更是看了几遍也很难消化。本文旨在经过一篇文章来让咱们看懂并上手Dagger。
Dagger最早由JakeWharton在square公司开发。后来转由Google保护并发展为Dagger2。Dagger2差异于Dagger1的当地主要在于两个,一个是由运行时经过反射构建依靠联系变为编译期经过注解生成依靠联系,另一个是犯错时有更好地提醒(当然这也是因为Dagger2在编译期间依据注解生成好了可读性较好的代码带来的优势)。 转载请注明来历「Bug总柴」
参阅
初学者主张先不要看官方文档,能够先看这几篇博客:
- Dagger 2 完全解析系列
- Dagger2 最明晰的运用教程
- Dagger 2 for Android Beginners系列
依靠注入
在学习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中下载:github.com/shenguojun/…