初识dagger(一)

773 阅读5分钟

依赖注入

依赖注入:在项目开发中,常见一个类的实例依赖另一个类实例的情况 如

public  class Man(){
	prviate Car car;
	public Man(){
		car=new Car();
	}
}

常见的依赖方式基于构造方法,set设置等

依赖注入是控制反转的一种表现形式,被依赖的对象(Car)不再由 依赖对象(Man)主动创建 ,而是由第三方容器去创建,然后注入到依赖对象(man)的过程,降低代码耦合性

集成与使用

在build.gradle中添加依赖:

dependencies {
    ...
    implementation 'com.google.dagger:dagger:2.13'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
}

借助上面的场景进行应用

Car相关代码 由于Car需要被注入,在构造方法上增加 @Inject 注解

public class Car {
  private CarDipan carDipan;

  @Inject
  public Car() {
  }

  public void pao() {
    System.out.println("跑起来");
  }
}

Man相关代码 在Car变量上声明 @Inject 注解

public class Man {
  @Inject
  Car car;

  public Man() {
    //该行代码后面讲到
    DaggerManComponent.create().inject(this);
  }

  public void kaiche() {
    car.pao();
  }
}

此时依赖和被依赖的类都创建好了,还缺少一个纽带把他们连接起来,纽带就是 @Component
@Component 注解需要作用到接口或者抽象类上,添加抽象方法,参数是需要注入依赖的类,会自动生成以Dagger开头的实现类 在Man的构造方法中进行注入 DaggerManComponent.create().inject(this); (可能会报找不到该类,需要rebuild下) ManComponent代码如下

@Component
interface ManComponent {
  void inject(Man man);
}

测试代码

public class DaggerTest {
  public static void main(String[] args) {
    Man man = new Man();
    man.kaiche();
  }
}

Module和Provides注解

有一些第三方库,我们无法修改源码,即无法用@Inject去注解构造方法。这时候就需要@Module和@Provides注解

  • @Module 注解到类上,表示这个类可以作为一个模块去引入到被@Component注解的接口上,规定用@Module注解的类名以Module做后缀
  • @Provides用于被@Module标注类的方法中,规定用provide作为前缀
    假设Man对象包含一个FangZi对象,该对象构造方法无法用@Inject注解标注,那么可以提供以下类
@Module
public class FangModule {
  @Provides
  public Fang provideFang() {
    return new Fang();
  }
}

对应的ManComponent类也需要修改

@Component(modules = FangModule.class)
interface ManComponent {
  void inject(Man man);
}

这样Man里面的房子属性就会成功注入。被注入的属性一定要用@Inject注解
Module类的构造方法可以带有参数,需要注意的是当Module类有带有参数的构造方法时, DaggerManComponent不会有Create方法,需要通过DaggerManComponent.builder().fangModule(new FangModule(参数)).build()手动传入module对象

Bind注解

Bind注解和Provides注意类似 ,也是放到被Module类的方法上,不同的是Bind注解的方法是抽象方法,参数类型是返回值类型的子类。 用于注入某个接口或者抽象类时,可以获取他的实现子类。或者可以说将一种类型转换成他的父类或接口,只暴露父类或接口的方法

BindModule.java

@Module
public abstract class BindModule {
  @Binds
  public abstract Manger bindManager(ManagerImpl manager);
  
  public static abstract class Manger {
    public abstract String getName();
  }

  public static class ManagerImpl extends Manger {
    @Inject
    public ManagerImpl() {
    }

    @Override public String getName() {
      return "我是ManagerImpl";
    }
  }
}
Man.java

public class Man {

  @Inject
  BindModule.Manger manger;//这是获取的时他的子类MangerImpl

  public Man() {
    DaggerManComponent.create().inject(this);
  }

  public void run() {
    System.out.println(manger.getName());
  }
}

Named、Singleton和Reusable

@Named 别名注解 和@Provides配合使用 比如我有两套房子,但是返回值都是房子 如果不用@Named注解,dagger不知道咱们要哪个房子,就会迷失,这时就需要起一个别名

@Module
public class FangModule {

  @Named("北三环")
  @Provides
  public Fang provideFang() {
    return new Fang("北三环");
  }
  @Named("西四环")
  @Provides
  public Fang provideFang2() {
    return new Fang("西四环");
  }
}

在获取房子是就必须注明@Named 表示 要哪一个

public class Man {
  @Inject
  Car car;
  @Named("北三环")
  @Inject
  Fang fang1;
  @Named("西四环")
  @Inject
  Fang fang2;
}

@Qualifier 元注解 注解在@Named上 我们可以用 Qualifier自定义和 @Named功能相同的 注解
@Singleton 单例注解 在一个Component组件中单例 Singleton标注在Component上 和 Provides标注的方法或者标注在要注入的类上。 实现机制 是通过DoubleCheck来保证单例,在生成的DaggerManComponent类的初始化中,会把Provider转换为DoubleCheck 在DoubleCheck的get方法中保证实例唯一

@Module
public class FangModule {
  @Singleton
  @Provides
  public Fang provideFang() {
    return new Fang("北三环");
  }
}
@Singleton
@Component(modules = FangModule.class)
interface ManComponent {
  void inject(Man man);
}

@Scope 元注解 注解在@Singleton 我们可以用 @Scope自定义和 @Singleton功能相同的 注解
@Reusable 注解 有时我们为了限制对象的创建次数,使用时可以从缓存中获取对象,可以使用该注解 该注解标注在 Provides标注的方法或者标注在要注入的类上。 该机制会把Provider转换为SingleCheck来 使用SingleCheck来保证如果无对象会创建对象 如果有则会从缓存中获取

@Module
public class FangModule {
  @Reusable
  @Provides
  public Fang provideFang() {
    return new Fang("北三环");
  }
}
@Component(modules = FangModule.class)
interface ManComponent {
  void inject(Man man);
}  

Lazy和Provider

Lazy 有时候我们不想运行时就把类创建好,而是在需要的时候创建类,这是就可以通过Lazy包装要被注入的成员 ,使用时通过get方式获取

public class Man {
  @Inject
  Lazy<Car> car;

  public Man() {
    DaggerManComponent.create().inject(this);
  }

  public void kaiche() {
    car.get().pao();
  }
} 

Provider 有时我们 需要创建类的多个实例,可以通过Provider包装要被注入的成员,每次执行get则会创建一个新的实例

IntoSet和IntoMap注解

IntoSet需要和Provides或Bind一起使用,我们知道在Module中如果有两个方法返回值会导致依赖迷失,需要用Named注解起别名。 但是如果这些方法被@IntoSet注解修饰,相同的返回值产生的对象会被放到同一个Set集合中。同时注入该类对象时,需要用Set去包装

FangModule.java

@Module
public class FangModule {
  @IntoSet
  @Provides
  public Fang provideFang1() {
    return new Fang("国贸");
  }

  @IntoSet
  @Provides
  public Fang provideFang2() {
    return new Fang("望京");
  }
}

Man.java

public class Man {
  @Inject
  Set<Fang> fangs;

  public Man() {
    DaggerManComponent.builder().fangModule(new FangModule()).build().inject(this);
  }

  public void run() {
    fangs.forEach(Fang::get);
  }
}

IntoMap和IntoSet类似,不过是将发返回的对象放入到map集合中。用IntoMap注解时, 必须指明Key.使用 @IntKey/ @LongKey/@StringKey注解 也可以使用@MapKey元注解,自定义Key注解