开门见山,来看一段常用业务代码。
以电脑为例。每一台电脑都需要CPU。所以在定电脑的时候,需要将CPU作为参数传入。
public class Computer {
CPU cpu;
public Computer(CPU cpu) {
this.cpu = cpu;
}
public CPU getCpu() {
return this.cpu;
}
}
public class CPU {
public CPU() {
}
public void run() {
System.out.println("CPU 运行起来了");
}
}
运行示例:
Computer car = new Computer(new CPU());
car.getCpu().run();
可以看到,我们如果要初始化Car对象,需要手动以构造的方式传入Engine对象,Car 与Engine的耦合严重。
其实Dagger2起源于Dagger ,是google基于 Dagger 优化之后,基于Java 注解的开源库。Dagger 2 会在编译阶段完成代码的生成。
主要用于模块间的解耦合,提高代码质量等。
为了解决这些问题,Dagger2应运而生。
其实说白了,Dagger2 是基于APT(annotation processing tool 注解处理工具)工具实现(Arouter),后续有空的话,咱们再聊聊APT。这次,咱先说说Dagger2的一些用注解。
这个注解,顾名思义,就是注入,那就是用来注解被注入的对象。
我们可以想像成打针,就是要把液体注入到患者体内(将CPU 注入到电脑内)。
那以上面的电脑与CPU为例,则CPU是被注入的对象。则咱们得在CPU的构造函数上加上@Inject的注解。
public class CPU {
@Inject
public CPU() {
}
public void run() {
System.out.println("CPU 运行起来了");
}
}
接下来,我们需要创建一个用@Component标注的接口ComputerComponent,这个ComputerComponent其实就是一个注入器,用于将CPU注入到电脑中。
@Component
public interface ComputerComponent {
/**
* 这个方法的的名字可以自己主,但是因为这个是注入器,所以比较习惯命名为inject
*
* @param computer 注入的对象(target)
*/
void inject(Computer computer);
}
最后,我们的Computer类,就改造成这样。
public class Computer {
@Inject
CPU cpu;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public CPU getCpu() {
return this.cpu;
}
}
编写测试代码
public class Main {
public static void main(String[] args) {
Computer computer = new Computer();
computer.getCpu().run();
}
}
在之前的示例中,由于CPU这个类是我们自己创建的,所以可以很简单的在其构造函数上加上@Inject注解。
那如果说,我们现在不能直接修改CPU这个类,那这个时候需要怎么办?
这个时候 ,我们就用到@Module与 @Provides注解 。
其实,Module 可以理解为一个模块中,所有依赖对像的集合。或者说,被@Module注解的类,充当着管理该Module的依赖的作用。
而Provides,从字面意思就知道,就是提供(依赖的对象的)。
首先,我们先去掉CPU构造函数的注解。(因为我们此时假设,这两个类是不能让我们直接修改的。)
public class CPU {
public CPU() {
}
public void run() {
System.out.println("CPU 运行起来了");
}
}
接下来,新建一个ComputerModle类。
@Module
public class ComputerModule {
@Provides
CPU providerCPU(){
return new CPU();
}
}
接下来,修改之前的ComputerComponent。
@Component(modules = ComputerModule.class)
public interface ComputerComponent {
/**
* 这个方法的的名字可以自己主,但是因为这个是注入器,所以比较习惯命名为inject
*
* @param computer 注入的对象(target)
*/
void inject(Computer computer);
}
可以看到,components 后面的参数其实是一个module集合,也就是说,我们可以通过@Component,用来管理多个modules。
之前说过,@Module是用来管理依赖的,我们假设现在有一个Keyborad类,然后Computer类需要依赖该类,则ComputerModule类可以写成这样。
@Module
public class ComputerModule {
@Provides
CPU providerCPU(){
return new CPU();
}
@Provides
Keyboard providerKeyBord(){
return new Keyboard();
}
}
那么现在又有一种情况,假如我的电脑需要有一个外接键盘以前一个自带的键盘(对,我的电脑是笔记本!问就是macbook pro),此时,我们该如何解决?
这个时候,@Qualifier就可以派上用场了。
首先,我们利用该注解,去定义两个注解。
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierOne {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierTwo {
}
接下来,我们修改一下Module。
@Module
public class ComputerModule {
@Provides
CPU providerCPU(){
return new CPU();
}
@QualifierOne
@Provides
Keyboard providerKeyBordOr(){
return new Keyboard("原生");
}
@QualifierTwo
@Provides
Keyboard providerKeyBordOut(){
return new Keyboard("外接");
}
}
然后就是Computer的代码了。
public class Computer {
@Inject
CPU cpu;
@QualifierOne
@Inject
Keyboard keyBord1;
@QualifierTwo
@Inject
Keyboard keyBord2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public CPU getCpu() {
return this.cpu;
}
public Keyboard getKeyBord1() {
return keyBord1;
}
public Keyboard getKeyBord2() {
return keyBord2;
}
}
接下来就是测试代码:
Computer computer = new Computer();
computer.getCpu().run();
computer.getKeyBord1().install();
computer.getKeyBord2().install();
可以看到,Qualifier的作用,其实就是,为依赖方指定生成方式。
通过该注解, 我们可以实现局部单例。
首先,我们先通过@Scope,定义一个ComputerScope注解。
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ComputerScope {
}
之后,我们用该注解,去标记之前的依赖提供者。在该示例中,我标记的是提供CPU的方法。
@Module
public class ComputerModule {
@ComputerScope
@Provides
CPU providerCPU(){
return new CPU();
}
@QualifierOne
@Provides
Keyboard providerKeyBordOr(){
return new Keyboard("原生");
}
@QualifierTwo
@Provides
Keyboard providerKeyBordOut(){
return new Keyboard("外接");
}
}
同时,我们也需要对Component进行标记。
@ComputerScope
@Component(modules = ComputerModule.class)
public interface ComputerComponent {
/**
* 这个方法的的名字可以自己主,但是因为这个是注入器,所以比较习惯命名为inject
*
* @param computer 注入的对象(target)
*/
void inject(Computer computer);
}
最后,我们简单修改一下Computer类,让其拥有两个CPU的成员变量。
public class Computer {
@Inject
CPU cpu;
@Inject
CPU cpu2;
@QualifierOne
@Inject
Keyboard keyBord1;
@QualifierTwo
@Inject
Keyboard keyBord2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public CPU getCpu() {
return this.cpu;
}
public Keyboard getKeyBord1() {
return keyBord1;
}
public Keyboard getKeyBord2() {
return keyBord2;
}
}
为了便于测试,我们简单修改一下CPU。
public class CPU {
public CPU() {
System.out.println("初始化CPU");
}
public void run() {
System.out.println("CPU 运行起来了");
}
}
测试代码:
Computer computer = new Computer();
computer.getCpu().run();
System.out.println(computer.getCpu().hashCode());
System.out.println(computer.getCpu2().hashCode());
System.out.println(computer.getCpu2().equals(computer.getCpu()));
输出
初始化CPU
CPU 运行起来了
1956725890
1956725890
true
可见,此时,只实例化了一个CPU对象。
其实一开始,我以为这个可能和我们平时写的单例是一个意思,直到我点开他的源码。
/**
* Identifies a type that the injector only instantiates once. Not inherited.
*
* @see javax.inject.Scope @Scope
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
是不是和咱们之前写的@ComputerScope 一毛一样。emmm,所以,功能也一样了,可以用来实现局部单例。
那至于如何实现全局单例,这个可以放到后面的Dagger Android中来讨论。
电脑的CPU,键盘都有了,现在需要为电脑提供一个屏幕了。此时,我们希望屏幕的具体类型通过参数的形式传入。
@Module
public class ComputerModule {
@Provides
IScreen providerScreen(AppleScreen screen){
return screen;
}
}
但如果用@Bind注解,我们可以让这个看起来更好看。
@Module
public abstract class ComputerModule {
@Binds
abstract IScreen bindAppScreen(AppleScreen screen);
}
需要注意的是,如果用@Bind注解,需要将该类定义为抽象类,并且被@Bind注解的方法为抽象方法。同时,参数 AppleScreen ,需要是
返回值ISreen的实现。
再然后,AppleScreen 的构造函数,需要被@Inject标记。
public class AppleScreen implements IScreen{
@Inject
public AppleScreen() {
}
@Override
public void show() {
System.out.println("苹果显示器");
}
}
当然,如果说想让@Provides与 @Bind在一个@Module中共存,则需要将@Provides 标记的方法的修饰符上加上 static,否则,运行是会报错的。
@Module
public abstract class ComputerModule {
@ComputerScope
@Provides
static CPU providerCPU(){
return new CPU();
}
@QualifierOne
@Provides
static Keyboard providerKeyBordOr(){
return new Keyboard("原生");
}
@QualifierTwo
@Provides
static Keyboard providerKeyBordOut(){
return new Keyboard("外接");
}
@Binds
abstract IScreen bindAppScreen(AppleScreen screen);
}
其实这个看他的类名就知道其作作是啥了。具体使用也比较简单。
public class Computer {
@Inject
Lazy<CPU> cpu;
@Inject
Lazy<CPU> cpu2;
@QualifierOne
@Inject
Keyboard keyBord1;
@QualifierTwo
@Inject
Keyboard keyBord2;
@Inject
AppleScreen appleScreen;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public CPU getCpu() {
return this.cpu.get();
}
public Keyboard getKeyBord1() {
return keyBord1;
}
public Keyboard getKeyBord2() {
return keyBord2;
}
public CPU getCpu2() {
return cpu2.get();
}
public AppleScreen getAppleScreen() {
return appleScreen;
}
}
我们简单的把Computer修改成上面的样子。
然后在编写测试代码:
Computer computer = new Computer();
computer.getAppleScreen().show();
computer.getCpu2().run();
输出:
苹果显示器
初始化CPU
CPU 运行起来了
可以看到,此时,CPU是在我们调用之后才初始化的。