Dagger
中使用了很多注解:
@Module
:Modules
类里面的方法专门提供依赖,所以我们定义一个类,用@Module
注解,这样Dagger
在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules
的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app
中可以有多个组成在一起的modules
)@Provide
:在modules
中,我们定义的方法是用这个注解,以此来告诉Dagger
我们想要构造对象并提供这些依赖。@Singleton
:当前提供的对象将是单例模式 ,一般配合@Provides
一起出现@Component
:用于接口,这个接口被Dagger2
用于生成用于模块注入的代码@Inject
:在需要依赖的地方使用这个注解。(你用它告诉Dagger
这个构造方法,成员变量或者函数方法需要依赖注入。这样Dagger
就会构造一个这个类的实例并满足他们的依赖。)@Scope
:Scopes
可是非常的有用,Dagger2
可以通过自定义注解限定注解作用域。
这个东西不太好入门,这里就直接用例子上代码了。有关如何添加以来在github
上面写的很清楚了,android
在build.gradle
中进行配置就好了。
官方demo
描述的是:一个泵压式咖啡机(CoffeeMaker)
由两个主要零件组成,泵浦(Pump)
和加热器(Heater)
,咖啡机有一个功能是煮泡咖啡(brew)
,
当进行煮泡咖啡时,会按如下几个步骤进行:
- 打开加热器进行加热
- 泵浦加压
- 萃取出咖啡
- 然后关闭加热器
一杯咖啡就算制作完毕了。
好了,那我们先开始写代码了(我把官方的示例代码进行了简化,方便更好的理解):
定义加热器接口:
interface Heater {
void on();
void off();
}
具体实现类:
class ElectricHeater implements Heater {
@Override
public void on() {
System.out.println("~ ~ ~ heating ~ ~ ~");
}
@Override
public void off() {
}
}
定义泵浦接口:
interface Pump {
void pump();
}
具体实现类:
class Thermosiphon implements Pump {
Thermosiphon() {
}
@Override
public void pump() {
System.out.println("=> => pumping => =>");
}
}
咖啡机功能:
class CoffeeMaker {
private final Heater heater;
private final Pump pump;
CoffeeMaker() {
heater = new ElectricHeater();
pump = new Thermosiphon();
}
public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.off();
}
}
好了完成了,如果执行的话运行结果是:
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P
完美,上面是我们普通的开发方式,那如果我们要用dagger
来实现该怎么弄呢?
首先这几个类是没有改动的,因为这几个类中没有牵扯到其他的类:
interface Pump {
void pump();
}
interface Heater {
void on();
void off();
}
class ElectricHeater implements Heater {
@Override
public void on() {
System.out.println("~ ~ ~ heating ~ ~ ~");
}
@Override
public void off() {
}
}
下面就开始使用dagger
需要改动的部分,首先创建module
类。@Module
类的命名惯例是以Module
作为类名称的结尾。而@Module
类中的@Provides
方法名称的命名惯例是以provide
作为前缀。
而这个Module
是干什么的呢?Module
管理所有的以来,这里你做咖啡需要以来泵浦(Pump)
和加热器(Heater)
,所以可以创建Module
把他们Pump
和Heater
管理起来,方便
之后获取Pump
和Heater
对象,所以它里面会有provideXXX()
的函数。
- 第一步:增加
Module
类,使用@Module
声明类(可以将Module理解成生产对象的工厂,里面提供了provideXXX
这种生产对象的方法)
@Module // Module注明该类是Module类
public class CoffeeModule {
@Provides // Provides注明该方法是用来提供依赖对象的方法
Heater provideHeater() {
return new ElectricHeater();
}
@Provides
Pump providePump() {
return new Thermosiphon();
}
}
- 第二步:增加接口
Component
,使用@Component
声明(Componet
是将Module
中生产的对象注入到对应的需要使用该对象的类中)
// 这里是声明要从CoffeeModule中去找对应的依赖,从`CoffeeModule`中去通过`provideXXX`方法来获取对应的对象
@Component(modules = CoffeeModule.class)
// 该接口会在编译时自动生成对应的实现类,这里是DaggerCoffeeComponent
public interface CoffeeComponent {
// 提供一个供目标类使用的注入方法,该方法表示要将Module中的管理类注入到哪个类中,这里当然是CoffeeMaker,因为我们要用他俩去生产咖啡
void inject(CoffeeMaker maker);
}
注意:@Component(modules = {AModule.class,BModule.class})
可以设置多个Module
,而且也可以@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
指定依赖的Module
和父Component
- 第三步:
Inject
注入
import javax.inject.Inject;
class CoffeeMaker {
@Inject // 标记该对象是被注入的
Heater heater;
@Inject
Pump pump;
CoffeeMaker() {
// DaggerCoffeeComponent这个类会在编译时产生,所以可以build一下
CoffeeComponent component = DaggerCoffeeComponent.create();
// 注入
component.inject(this);
// 或者使用下面的代码也是可以的
// DaggerCoffeeComponent.builder()
// .coffeeModule(new CoffeeModule())
// .build()
// .inject(this);
}
public void brew() {
heater.on();
pump.pump();
System.out.println(" [_]P coffee! [_]P ");
heater.off();
}
}
到这里就完成了,执行的话,结果是一样的:
04-20 15:16:55.083 16375-16375/com.charon.stplayer I/System.out: ~ ~ ~ heating ~ ~ ~
04-20 15:16:55.084 16375-16375/com.charon.stplayer I/System.out: => => pumping => =>
[_]P coffee! [_]P
到这里你肯定就迷糊了,为什么? 本来很简单的东西,你还多搞出来几个类,弄的这么麻烦。但是麻烦吗? 你要是有十个地方呢? 一百个地方呢? 以后你修改怎么改? 一百个地方都改吗?
好了,最后来个总结,高中老师讲的,要来个综上所述:
- 通过
@Module
声明一个XXXModule
类,用于管理所有依赖,并使用@Provides
为每个依赖提供provideXXX()
方法 - 通过
@Component
声明一个XXXComponent
接口,并且声明要从哪些Module
中寻找依赖- 声明要去哪些
Module
中寻找依赖@Component(modules = CoffeeModule.class)
- 提供一个供目标类使用的注入方法
void inject(CoffeeMaker maker);
- 为所有的依赖添加一个方法
这个说起来就有点多了,而且我们上面的代码中并没有进行这一步操作,这是因为方法也可以不写,但是如果要写,就按照这个格式来
但是当
Component
要被别的Component
依赖时,这里就必须写这个方法,不写代表不向别的Component
暴露此依赖,而且如果要写的话 这些方法返回值必须是从上面指定的依赖库CoffeeModule.class
中取得的对象,注意:而方法名不一致也行,但是方便阅读,建议一致,因为它主要是根据返回值类型来找依赖的。 这里如果要写的话就是Heater provideHeater(); Pump providePump();
- 声明要去哪些
- 目标类使用依赖注入,首先是使用
@Inject
声明属性变量,表示注入这个依赖,然后是调用inject()
方法注入依赖。
下面来个图,能够更方便的去理解:
Container
就是可以被注入的容器,具体对应上文例子中的CoffeeMaker
,Container
拥有需要被初始化的元素。需要被初始化的元素必须标上@Inject
,只有被标上@Inject
的元素才会被自动初始化。@Inject
在Dagger2
中一般标记构造方法与成员变量。Module
可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从Module
生产的。- 有了可以被注入的容器
Container
,也有了提供依赖对象的Module
。我们必须将依赖对象注入到容器中,这个过程由Component
来执行。Component
将Module
中产生的依赖对象自动注入到Container
中。
好像大体明白了点....
上面有DaggerBaseComponent.create();
和DaggerBaseComponent.builder().build()
两种方式有什么区别呢? 我们看一下源码:
public static Builder builder() {
return new Builder();
}
public static BaseComponent create() {
return new Builder().build();
}
Dagger2
会自动生成该接口的实现类(以Dagger
开头,如果@Component
注解类不是顶级类,自动生成的实现类的名字会闭包命名并用下划线分割如DaggerFoo_Bar_BazComponent)
,可以通过该实现类的builder()
方法获得builder
对象,builder
对象可以设置好依赖,最后通过build()
方法构建实例:
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
如果Modules
都是缺省构造器,@Provides
方法都是静态的,用户不需要构建依赖实例,那么自动生成的实现类就会有create()
方法获取新实例,就不需要处理builder
了。
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}
- 邮箱 :charon.chui@gmail.com
- Good Luck!