前言 、去吃火锅了
天气这么冷,叫上几个小伙伴去吃火锅吧。
1.先定义好4个类:Meat,Vegetable,Seafood,Soup
2.定义Hotpat类,里面有个方法叫makeDinner(),这个方法需要上面的4个参数,这4个参数在Hotpot构造函数中创建.
public class Meat {
public Meat(){
KLog.d("肉类");
}
}
public class Seafood {
public Seafood() {
KLog.d("海鲜");
}
}
public class Vegetable {
public Vegetable() {
KLog.d("蔬菜");
}
}
public class Soup {
String spicy;//辣的
public Soup() {
KLog.d("清淡的锅底");
}
public Soup(String spicy) {
this.spicy = spicy;
KLog.d("加辣的锅底");
}
}
public class Hotpot {
private final Soup soup;
private final Meat meat;
private final Vegetable vegetable;
private final Seafood seafood;
public Hotpot() {
soup = new Soup();
meat = new Meat();
vegetable = new Vegetable();
seafood = new Seafood();
makeDinner(soup,meat,vegetable,seafood);
}
private void makeDinner(Soup soup, Meat meat, Vegetable vegetable, Seafood seafood) {
KLog.d("吃火锅了" );
}
}
简单易懂的写法,火锅制作出来了。
一、我们来看看用Dagger2的写法。
按照国际惯例,先给出配置:
compile 'com.google.dagger:dagger:2.+'
annotationProcessor 'com.google.dagger:dagger-compiler:2.+'
1.1 第一种写法
- 增加一个类HotpotModule
首先声明:前面写的四个被依赖的类(Soup、Meat、 Vegetable、Seafood),不用做任何改动
下面要用到两个关键的注解@Module、@Provides
@Module
public class HotpotModule {
@Provides
public Meat provideMeat(){
return new Meat();
}
// 返回值(被依赖的类类型)
// 方法名(provideXxx必须以provide开头,后面随意)
@Provides
public Soup provideSoup(){
return new Soup();
}
@Provides
public Vegetable provideVegetable(){
return new Vegetable();
}
@Provides
public Seafood provideSeafood(){
return new Seafood();
}
}
2.增加一个接口HotpotComponent
@Component(modules = {HotpotModule.class})
public interface HotpotComponent {
//注意:下面这三个方法,返回值必须是从上面指定的依赖库HotpotModule.class中取得的对象
//注意:而方法名不一致也行,但是方便阅读,建议一致,因为它主要是根据返回值类型来找依赖的
//★注意:下面这三个方法也可以不写,但是如果要写,就按照这个格式来
//但是当Component要被别的Component依赖时,
//这里就必须写这个方法,不写代表不向别的Component暴露此依赖
Meat provideMeat();
Soup provideSoup();
Vegetable prorideVegetable();
Seafood provideSeafood();
//注意:下面的这个方法,表示要将以上的四个依赖注入到某个类中
//这里我们把上面的三个依赖注入到Hotpot中
void inject(Hotpot hotpot);
}
- 重写Hotpat
public class Hotpot {
//不能有修饰符
@Inject Soup soup;
@Inject Meat meat;
@Inject Vegetable vegetable;
@Inject Seafood seafood;
public Hotpot() {
// DaggerHotpotComponent编译时才会产生这个类,
// 所以编译前这里报错不要着急(或者现在你先build一下)
DaggerHotpotComponent.builder()
.hotpotModule(new HotpotModule())
.build()
.inject(this);
makeDinner(soup,meat,vegetable,seafood);
}
private void makeDinner(Soup soup, Meat meat, Vegetable vegetable, Seafood seafood) {
KLog.d("吃火锅了" );
}
}
测试:
12-25 13:59:57.545 6206-6206/com.example.myapplication D/lzx: [ (Soup.java:14)#<init> ] 清淡的锅底
12-25 13:59:57.545 6206-6206/com.example.myapplication D/lzx: [ (Meat.java:12)#<init> ] 肉类
12-25 13:59:57.545 6206-6206/com.example.myapplication D/lzx: [ (Vegetable.java:12)#<init> ] 蔬菜
12-25 13:59:57.546 6206-6206/com.example.myapplication D/lzx: [ (Seafood.java:12)#<init> ] 海鲜
12-25 13:59:57.546 6206-6206/com.example.myapplication D/lzx: [ (Hotpot.java:30)#makeDinner ] 吃火锅了
1.2 第二种写法
- 修改依赖的4个类 ,例如Meat,其他三个类同样是在构造方法上加@Inject注解
public class Meat {
@Inject
public Meat(){
KLog.d("肉类");
}
}
2.增加一个接口HotpotComponent
@Component
public interface HotpotComponent {
void inject(Hotpot hotpot);
}
- 重写Hotpat
public class Hotpot {
//不能有修饰符
@Inject Soup soup;
@Inject Meat meat;
@Inject Vegetable vegetable;
@Inject Seafood seafood;
public Hotpot() {
// DaggerHotpotComponent编译时才会产生这个类,
// 所以编译前这里报错不要着急(或者现在你先build一下)
DaggerHotpotComponent.builder()
.build()
.inject(this);
makeDinner(soup,meat,vegetable,seafood);
}
private void makeDinner(Soup soup, Meat meat, Vegetable vegetable, Seafood seafood) {
KLog.d("吃火锅了" );
}
}
@Inject标记构造函数就可以提供依赖了么,为什么还需要@Module?很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject注解
二、带参数怎么办
下文以上面的第一种写法为例
如果被依赖类的构造函数带有参数,要把这个参数的类型也管理起来
吃火锅怎能没有啤酒Beer呢,但是Beer的构造函数里需要传入一个Cup杯子,那么如何注入这个Beer呢
1.新增两个类
public class Cup {
public Cup() {
KLog.d("酒杯");
}
}
public class Beer {
Cup cup;
public Beer(Cup cup) {
this.cup = cup;
KLog.d("酒杯里的啤酒");
}
}
2.修改HotpotModule里的依赖
@Module
public class HotpotModule {
...
//引入构造函数带参数的依赖
@Provides
public Beer provideBeer(Cup cup){
return new Beer(cup);
}
@Provides
public Cup provideCup(){
return new Cup();
}
}
3.修改Component
@Component(modules = {HotpotModule.class})
public interface HotpotComponent {
//Beer provideBeer(Cup cup);//★注意:这里千万不能带参数,否则报错
Beer provideBeer();
Cup provideCup();
//注意:下面的这个方法,表示要将以上的四个依赖注入到某个类中
//这里我们把上面的三个依赖注入到Hotpot中
void inject(Hotpot hotpot);
}
4.在目标类Hotpot里注入依赖
public class Hotpot {
//不能有修饰符
@Inject Soup soup;
@Inject Meat meat;
@Inject Vegetable vegetable;
@Inject Seafood seafood;
@Inject Beer beer;
//@Inject Cup cup; //如果需要用到Cup,这里才需要注入
public Hotpot() {
// DaggerHotpotComponent编译时才会产生这个类,
// 所以编译前这里报错不要着急(或者现在你先build一下)
DaggerHotpotComponent.builder()
.hotpotModule(new HotpotModule())
.build()
.inject(this);
makeDinner(soup, meat, vegetable, seafood, beer);
}
private void makeDinner(Soup soup, Meat meat, Vegetable vegetable, Seafood seafood, Beer beer) {
KLog.d("吃火锅配啤酒");
}
}
测试:
12-25 14:39:04.573 7496-7496/com.example.myapplication D/lzx: [ (Soup.java:14)#<init> ] 清淡的锅底
12-25 14:39:04.573 7496-7496/com.example.myapplication D/lzx: [ (Meat.java:12)#<init> ] 肉类
12-25 14:39:04.573 7496-7496/com.example.myapplication D/lzx: [ (Vegetable.java:12)#<init> ] 蔬菜
12-25 14:39:04.573 7496-7496/com.example.myapplication D/lzx: [ (Seafood.java:12)#<init> ] 海鲜
12-25 14:39:04.573 7496-7496/com.example.myapplication D/lzx: [ (Cup.java:12)#<init> ] 酒杯
12-25 14:39:04.573 7496-7496/com.example.myapplication D/lzx: [ (Beer.java:14)#<init> ] 酒杯里的啤酒
12-25 14:39:04.574 7496-7496/com.example.myapplication D/lzx: [ (Hotpot.java:32)#makeDinner ] 吃火锅配啤酒
三、有多个构造函数,怎么办
其实在前面的Soup类中已经设置了埋点,我们把汤底分成清淡的、辛辣的 。我们可以用Dagger提供的@Qualifier限定符来解决这个问题。
@Named(“String”)也能解决这个问题,只不过,传递的值只能是字符串,用@Qualifier更灵活一点
1.自己定义限定符,区分是哪个构造函数的
/**
* 自定义一个限定符
*/
@Qualifier//限定符
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Type {
String value() default "";//默认值为""
}
- 修改HotpotModeule,使用限定符@Type来区分不同的构造函数new出来的对象。
@Module
public class HotpotModule {
...
@Type("normal")
@Provides
public Soup provideNormalSoup() {
return new Soup();
}
@Type("spicy")
@Provides
public Soup provideSpicySoup(String spicy) {
return new Soup(spicy);
}
//由于我们的Soup构造函数里使用了String,所以这里要管理这个String(★否则报错)
//int等基本数据类型是不需要这样做的
@Provides
public String provideString(){
return new String();
}
}
3.修改HotpotComponent
@Component(modules = {HotpotModule.class})
public interface HotpotComponent {
...
@Type("normal")
Soup provideNormalSoup();
@Type("spicy")
Soup provideSpicySoup();
String provideString();
}
4.在目标类Hotpot里注入Soup依赖,我们要分别注入两个构造函数new出的对象
public class Hotpot {
//不能有修饰符
@Inject
@Type("normal")
Soup normalSoup;
@Inject
@Type("spicy")
Soup spicySoup;
@Inject
Meat meat;
@Inject
Vegetable vegetable;
@Inject
Seafood seafood;
@Inject
Beer beer;
//@Inject Cup cup; //如果需要用到Cup,这里才需要注入
public Hotpot() {
// DaggerHotpotComponent编译时才会产生这个类,
// 所以编译前这里报错不要着急(或者现在你先build一下)
DaggerHotpotComponent.builder()
.hotpotModule(new HotpotModule())
.build()
.inject(this);
makeDinner(normalSoup ,spicySoup, meat, vegetable, seafood, beer);
}
private void makeDinner(Soup soup, Soup spicySoup, Meat meat, Vegetable vegetable, Seafood seafood, Beer beer) {
KLog.d("吃鸳鸯火锅配啤酒");
}
}
测试:
12-25 15:27:38.117 8880-8880/com.example.myapplication D/lzx: [ (Soup.java:14)#<init> ] 清淡的锅底
12-25 15:27:38.117 8880-8880/com.example.myapplication D/lzx: [ (Soup.java:21)#<init> ] 加辣的锅底
12-25 15:27:38.117 8880-8880/com.example.myapplication D/lzx: [ (Meat.java:12)#<init> ] 肉类
12-25 15:27:38.118 8880-8880/com.example.myapplication D/lzx: [ (Vegetable.java:12)#<init> ] 蔬菜
12-25 15:27:38.118 8880-8880/com.example.myapplication D/lzx: [ (Seafood.java:12)#<init> ] 海鲜
12-25 15:27:38.118 8880-8880/com.example.myapplication D/lzx: [ (Cup.java:12)#<init> ] 酒杯
12-25 15:27:38.118 8880-8880/com.example.myapplication D/lzx: [ (Beer.java:14)#<init> ] 酒杯里的啤酒
12-25 15:27:38.118 8880-8880/com.example.myapplication D/lzx: [ (Hotpot.java:46)#makeDinner ] 吃鸳鸯火锅配啤酒
四、想要单例模式怎么办
1.在HotpotModule里,在provideXXX方法前添加@Singleton即可:
@Module
public class HotpotModule {
@Singleton
@Provides
public Meat provideMeat() {
return new Meat();
}
...
}
2.在HotpotComponent里,在接口上添加@Singleton 注释 即可
@Singleton //注意是在接口上添加,注意位置
@Component(modules = {HotpotModule.class})
public interface HotpotComponent {
...
}
3.Hotpot类不变,测试:
public class Hotpot {
@Inject
Meat meat;
@Inject
Meat meat2;
...
private void makeDinner(Soup soup, Soup spicySoup, Meat meat, Vegetable vegetable, Seafood seafood, Beer beer) {
KLog.d("吃鸳鸯火锅配啤酒");
KLog.d("meat:"+meat.toString()+",meat2:"+meat2.toString());
}
}
结果证实是单例的!
12-25 15:37:12.738 9148-9148/com.example.myapplication D/lzx: [ (Hotpot.java:50)#makeDinner ] meat:com.example.myapplication.hot_pot.Meat@7513d90,meat2:com.example.myapplication.hot_pot.Meat@7513d90
五、 @Scope注解
dagger2中的Singleton其实就是Scope,可以查看Dagger2源码中Singleton的定义:
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
自定义的@Scope:
@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScoped {
}
Dagger2中@Singleton和自己定义的@ApplicationScoped代码上并没有什么区别,那为什么还要这么做呢? 是因为Dagger2需要保证Component和Module是匹配的,就需要用到这个注解。
那它是怎么做到单例的呢?查看DaggerXXXComponent中的initialize()方法,有@Scope类注解的@Provider生成的代码,外层多了一层DoubleCheck.provider(…);没有@Scope类注解的则是直接create一个新的实例。
private void initialize(final Builder builder) {
this.provideMeatProvider =
DoubleCheck.provider(HotpotModule_ProvideMeatFactory.create(builder.hotpotModule));
this.hotpotModule = builder.hotpotModule;
}
简单来说就是加了@Scope的Provider,Dagger会缓存一个实例在Dagger…Component中,在Dagger…Component中保持单例,缓存的provide跟随Dagger…Component的生命周期,Dagger…Component被销毁时,provider也被销毁,这就是局部单例的概念,假如你的Dagger…Component是在你应用的application中(因为Application只实例化一次!一次!一次!),则就形成了全局单例。
最面我再来介绍Component之间的依赖关系。
六、Component之间的依赖
继续回到吃火锅的剧情:吃完火锅后好需要吃些水果助消化。假如这个水果的依赖体系已经完成了(商家早就准备好的),现在火锅需要这个体系的一个对象作为依赖。这个水果的体系如下:
public class Fruit {
public Fruit() {
KLog.d("饭后水果");
}
}
@Module
public class FruitModule {
@Provides
public Fruit provideFruit(){
return new Fruit();
}
}
@Component(modules = {FruitModule.class})
public interface FruitComponent {
// ★前面说过这里的这个方法是可以不写的,
// 但是,如果你想让别的Component依赖这个Component,
// 就必须写,不写这个方法,就意味着没有向外界,暴露这个依赖
Fruit provideFruit();
//void inject(Object o);//这里的水果并没有想注入到那个类中,可以不写inject方法
}
那么FruitComponent和HotpotComponent是怎么联系起来的呢?
@Component(modules = {HotpotModule.class},dependencies = {FruitComponent.class})//引入FruitComponent
public interface HotpotComponent {
//其他的代码不改动
}
在Hotpot注入依赖。
public class Hotpot {
...
@Inject
Fruit fruit;
public Hotpot() {
// DaggerHotpotComponent编译时才会产生这个类,
// 所以编译前这里报错不要着急(或者现在你先build一下)
//必须先执行这个
FruitComponent fruitComponent = DaggerFruitComponent.builder().fruitModule(new FruitModule()).build();
DaggerHotpotComponent.builder()
.hotpotModule(new HotpotModule())
.fruitComponent(fruitComponent) // 在这里通过传入“fruitComponent(fruitComponent)”构建HotpotComponent,
.build()
.inject(this);
makeDinner(normalSoup, spicySoup, meat, vegetable, seafood, beer, fruit);
}
private void makeDinner(Soup soup, Soup spicySoup, Meat meat, Vegetable vegetable, Seafood seafood, Beer beer, Fruit fruit) {
KLog.d("吃鸳鸯火锅配啤酒");
KLog.d("吃饭后还有水果吃哦");
}
}
测试:
12-25 17:34:58.026 12142-12142/com.example.myapplication D/lzx: [ (Hotpot.java:57)#makeDinner ] 吃鸳鸯火锅配啤酒
12-25 17:34:58.026 12142-12142/com.example.myapplication D/lzx: [ (Hotpot.java:58)#makeDinner ] 吃饭后还有水果吃哦
这次火锅盛宴结束,我们一起总结下。
Dagger2使用过程中我们通常接触到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。
@Inject:@Inject有两个作用,一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖;二是用来标记构造函数,Dagger2通过@Inject注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject标记了的变量提供依赖;
@Module:@Module用于标注提供依赖的类。你可能会有点困惑,上面不是提到用@Inject标记构造函数就可以提供依赖了么,为什么还需要@Module?很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject注解,又比如说提供以来的构造函数是带参数的,如果我们之所简单的使用@Inject标记它,那么他的参数又怎么来呢?@Module正是帮我们解决这些问题的。
@Provides:@Provides用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject的变量赋值;
@Component:@Component用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被Component标注的接口在编译时会生成该接口的实现类(如果@Component标注的接口为CarComponent,则编译期生成的实现类为DaggerCarComponent),我们通过调用这个实现类的方法完成注入;
@Qulifier:@Qulifier用于自定义注解,也就是说@Qulifier就如同Java提供的几种基本元注解一样用来标记注解类。我们在使用@Module来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以provide开头,但这并不是强制的,只是为了增加可读性而已)。那么Dagger2怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2根据返回值的类型来决定为哪个被@Inject标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型Dagger2就懵逼了。@Qulifier的存在正式为了解决这个问题,我们使用@Qulifier来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject标注的变量),这样Dagger2就知道为谁提供依赖了。—-一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;
@Scope:@Scope同样用于自定义注解,我能可以通过@Scope自定义的注解来限定注解作用域,实现局部的单例;
@Singleton:@Singleton其实就是一个通过@Scope定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的Component是否为一个全局对象。
我们提到@Inject和@Module都可以提供依赖,那如果我们即在构造函数上通过标记@Inject提供依赖,有通过@Module提供依赖Dagger2会如何选择呢?具体规则如下:
步骤1:首先查找@Module标注的类中是否存在提供依赖的方法。
步骤2:若存在提供依赖的方法,查看该方法是否存在参数。
a:若存在参数,则按从步骤1开始依次初始化每个参数;
b:若不存在,则直接初始化该类实例,完成一次依赖注入。
步骤3:若不存在提供依赖的方法,则查找@Inject标注的构造函数,看构造函数是否存在参数。
a:若存在参数,则从步骤1开始依次初始化每一个参数
b:若不存在,则直接初始化该类实例,完成一次依赖注入。