这篇文章主要谈一下本人在学习Dagger2的心得,如有错漏,敬请谅解。
什么是依赖注入
依赖注入就是把下面这样的代码:
class A {
public A() {
}
class B {
A a;
public B() {
a = new A();
}
}
class C {
A a;
B b;
public C() {
a = new A();
b = new B();
b.a = a;
}
}
main() {
C c = new C();
}
变成:
class A {
A() {
}
}
class B {
A a;
B(A a) {
this.a = a;
}
}
class C {
A a;
B b;
C(A a, B b) {
this.a = a;
this.b = b;
}
}
main() {
A a = new A();
B b = new B(a);
C c = new C(a, b);
}
这种把对象之间的依赖生成的责任交给外界的做法,叫做依赖注入。
如何更方便地进行依赖注入
我们有类和它们之间的依赖关系,便很自然地会用图来表示这种状态。如上例子所示,可用下面这样一个图来表示:
+-----+
| |
+----> | A | <----+
| | | |
| +-----+ |
| |
| |
+---+---+ +---+---+
| | | |
| B | <---------+ C |
| | | |
+-------+ +-------+
箭头表示依赖的对象。
我们想要这样的一种依赖注入框架:当我们需要一个B对象时,框架按照依赖遍历这个图,生成A,然后将其注入B,最后返回一个已经生成好的B对象。大概是:
B b = Injector.create(B.class)
另外,如果要求A对象是单例(这里不解释什么是单例)或对象的生成符合某种指定的规则,框架应自动识别并作出处理。
设计框架
我们面对两个主要问题:如何表示依赖图和如何生成对象。
依赖图的表示
我们需定义一种声明依赖的方法。可以用xml,json,甚至DSL来完成这个任务。这里我们采用比较流行和简便的注解(annotation)来表示依赖关系。
假设我们要的效果如下所示:
@dependence
class A {
}
@dependence(A.class)
class B {
}
@dependence({A.class, B.class})
class C {
}
可以看到,我们用@dependence注解来表示上面例图中的箭头。各个类之间的依赖关系十分清晰。
如果要求A是单例,我们可以这样:
对象生成
建立了依赖图以后,需要通过某种方式生成我们需要的对象。我们希望是这样的:
或者通过注解实现自动注入
Dagger2
我们来看一下Dagger2是如何实现上述两个目标的。
依赖图的表示
Dagger2中,是通过@Inject注解或者@Module和@Provide这两个注解建立依赖图,如下所示:
首先定义好类:
public class A {
public A(){
}
}
public class B {
A a;
public B(A a) {
this.a = a;
}
}
public class C {
A a;
B b;
public C(A a, B b) {
this.a = a;
this.b = b;
}
}
然后我们用第一种方法来声明依赖:
public class A {
@Inject
public A() {
}
}
public class B {
A a;
@Inject
public B(A a) {
this.a = a;
}
}
public class C {
A a;
B b;
@Inject
public C(A a, B b) {
this.a = a;
this.b = b;
}
}
可以看到我们为每一个类的方法添加了@Inject声明,表示该类是依赖图中的一个节点。如果该初始化方法含有参数,那么这些从参数也应是依赖图中的节点。
第二种方法是通过一个module类来声明依赖,如下所示:
@Module
public class ABCModule {
@Provides
public A provideA() {
return new A();
}
@Provides
public B provideB(A a) {
return new B(a);
}
@Provides
public C provideC(A a, B b) {
return new C(a, b);
}
}
@Module注解表示这个ABCModule的作用是声明“依赖图”的。@Provides注解表示当前方法的返回值是图中的一个节点,方法的参数是依赖的对象,即前文中箭头指向的目标。
再强调一次,Dagger要求图中的每一个节点都要声明,即每一个节点都要在module中有@Provides注解的方法或者@Inject注解的初始化方法。
可以看到第二种方式(module)无需修改原来的对象。为了让模块尽量少地依赖第三方库,一般采用第二种方式来声明依赖图。
对象生成
Dagger2中,从依赖图中获取对象需通过component。component是依赖图和被注入对象之间的桥梁。如下所示:
@Component(module=ABCModule.class)
public interface ABCComponent {
public A provideA();
public B provideB();
public C provideC();
void inject(Activity mainActivity);
}
@Component注解表示ABCComponent这个接口是一个Component。Component的方法隐含着如下两条规则:
- 不带参数的方法为“provider”方法,该方法的返回值是从依赖图中取得的对象。如下所示(伪代码):
class Main {
}
-
带参数的方法,参数为“注入对象”。通常于@Inject标签同时使用。如下所示(伪代码):
class Main {
}
即调用Component.inject(foorbar)的时候,框架自动为用@Inject标签标注的属性注入依赖。要求@Inject的属性的类必须是依赖图中的节点。
注意:component的方法必需至少符合以上两条规则中的一条。
注意:provider方法的名字一般为“provider”,inject方法的名字一般为“inject”,但名字不影响这两个方法的功能。
当Component声明好以后,框架会在编译时生成一个DaggerComponent名字的类,我们可以用它来实施依赖注入,如下所示:
ABCComponent abcComponent = DaggerABCComponent.create();
A a = abcComponent.provideA();
B b = abcComponent.provideB();
C c = abcComponent.provideC();
或者:
class Main {
@Inject
A a;
@Inject
B b;
@Inject
C c;
public static void main() {
ABCComponent abcComponent = DaggerABCComponent.create();
abcComponent.inject(this);
}
}
Component标签的module属性可以是一个数组,即一个Component实施多个module的注入。引入类D和DModule:
class D {
public D() {
}
}
@Module
public class DModule {
@Provides
public D provideD() {
return new D();
}
}
修改ABCComponent,如下:
@Component(module={ABCModule.class, DModule.class})
public interface ABCComponent {
public A provideA();
public B provideB();
public C provideC();
public D provideD();
void inject(Activity mainActivity);
}
如上即可实现D对象的注入。
Component之间的依赖
真正实施工程的时候,会将对象以功能分类。例如network相关,DB相关,Util相关的类集中在一起管理。Dagger2为方便我们达到这一个目的,在component中引入了dependence这个功能。
例如我们有如下component
假设DComponent负责提供一个对象D。这种能力是项目无关的,我们把这个Component独立出来。然后我们可以通过@Component的dependence属性来为其他Component引入DComponent的能力。例如:
@Component(modules = ABCModule.class , dependencies = DComponent.class)
public interface ABCComponent {
A provideA();
B provideB();
C provideC();
D provideD();
void inject(Main main);
}
可以看到,声明了dependencies=DComponent.class以后,provideD方法可以顺利拿到D对象。inject方法也可以注入D对象。
public class Main {
@Inject
D d; // inject D by ABCComponent
public Main() {
DComponent dComponent = DaggerDComponent.create();
D d1 = dComponent.provideD(); // inject D by DComponent
ABCComponent abcComponent = DaggerABCComponent
.builder()
.dComponent(dComponent)
.build();
D d2 = abcComponent.provideD();
abcComponent.inject(this);
}
}
DComponent不知道ABCComponent的存在,故可以像普通Component那样子使用。但在使用ABCComponent时,我们需要显式地为ABCComponent注入DComponent对象:
@Singleton
如上面例子所示,如果要求D对象为单例,可以通过@Singleton注解来实现。首先我们需要在依赖图中声明对象是单例的:
DComponent接口也需要声明:
如此,当我们注入D对象时,可保证每次注入的是同一个D对象:
DComponent dComponent = DaggerDComponent.create();
D d1 = dComponent.provideD();
D d2 = dComponent.provideD();
// d1 == d2
总结
这篇文章只是简单地介绍了Dagger2的基本用法,下一篇打算讲Dagger2中的Scope和Subcomponent还在一些别的东西,敬请期待。
It’s appropriate time to make a few plans for the long run and it is time
to be happy. I’ve learn this submit and if I could I desire to counsel you few fascinating
things or advice. Maybe you could write next articles relating to this article.
I desire to learn more things approximately it!
May I just say what a comfort to uncover an individual who actually knows what they’re discussing on the web. You actually realize how to bring an issue to light and make it important. More people ought to check this out and understand this side of the story. I was surprised you are not more popular since you surely have the gift.
https://brides.com.ua/broneplivka-na-fary-realnyi-zakhyst-chy.html