PS: 本篇文章的示例代码,放在了项目的chapter3分支
上一篇文章我们介绍了使用@inject声明提供类型,并据此实现了Car和Engine的完全依赖注入,这篇文章我们使用Module来实现同样的效果。
我们先梳理一下上篇文章里的依赖关系,我们最后总结了它的依赖模型是这样的:
D(Car,Engine) = D(Car,DaggerCarComponent)
加上Dagger2托管的依赖关系实际上是这样的:
D(Car,Engine) = D(Car,DaggerCarComponent) + D(DaggerCarComponent,Car构造方法) + D(Car构造方法, Engine构造方法)
我们先调整Engine的提供,使用Module来实现。
定义EngineModule
首先我们聊一下什么是Module,Module是Dagger2为我们定义的一个角色,它是一个“提供类型P(?)”的容器,我们用@Module注解一个类来声明这个类是一个module,在这个类里面,所有用@Provides注解的返回类型不为空的方法都是合法的提供类型。要注意的是,Dagger2框架不为Null对象做任何的错误兼容处理,相反地,它会在注入前检查Module创建的对象是否为空,如果为空就报空指针错误,所以我们要确保Module的提供方法返回一个可用的对象。
如果我们想返回可能为空的对象,可以使用@Nullable注解声明,这个注解是类型递归的,使用了这个注解的方法,Component里也必须同样声明,否则会编译失败。
现在我们创建一个EngineModule来实现P(Engine)。
@Module
public class EngineModule {
@Provides
Engine provideEngine(){
return new Engine(2);
}
}
声明对EngineModule的依赖
创建后我们要告诉CarComponent使用EngineModule来作为它的提供类型的容器,查找它需要的提供类型。Component注解定义了modules索引的class数组参数,用来声明它依赖的所有Module。
这里和使用Inject注解构造方法不同之处在于,类的构造方法总是唯一的(参数类型唯一),因此它作为“提供类型”也是唯一的,所以Dagger2会自动将构造方法声明的提供类型,作为所有Component的备用类型,不用我们手动告诉具体的Component。但是如果在Module里声明提供类型,我们可以在多个Module里声明相同的提供类型,根据不同的使用需求对Component进行装配,因此多了告诉Component它的Modules依赖这一步,这一步也是我们使用Dagger2来管理依赖关系的优越性的体现:我们只要修改注解,就可以修改具体的依赖关系。
我们修改一下CarComponent接口:
@Component(modules = EngineModule.class)
public interface CarComponent {
void inject(Car car);
Car getCar();
}
现在我们使用Module声明了Engine的提供类型,main方法的调用方式保持不变:
public class Main {
public static void main(String[] args){
Car car = DaggerCarComponent.builder().build().getCar();
System.out.println("cylinderNumbers : " + car.getEngine().getCylinderNumbers());
}
}
我们运行一下:
cylinderNumbers : 2
Process finished with exit code 0
Engine的气缸数目已经变为了2,也就是说我们修改生效了。
Module的工作机制
现在我们再看看生成的注入代码,了解一下Module的工作机制。我们先看下getCar方法:
@Override
public Car getCar() {
return injectCar(
Car_Factory.newCar(EngineModule_ProvideEngineFactory.proxyProvideEngine(engineModule)));
}
对比使用@Inject声明的模板代码,我们发现前半部分
injectCar(Car_Factory.newCar(xxx));
是一样的,不同的是前者直接创建了一个Engine对象,而这里使用了模板来获取对象,Dagger为我们生成了与Module名称和方法名称对应关系为:
模块名_方法名Factory.proxy方法名(模块名 instance)
的静态类和方法,接受EngineModule对象,并作为它的代理来获取对象,我们看看代理方法:
public static Engine proxyProvideEngine(EngineModule instance) {
return Preconditions.checkNotNull(
instance.provideEngine(), "Cannot return null from a non-@Nullable @Provides method");
}
这里调用了Module实例中我们自己定义的provideEngine方法,并校验module生成的对象是否为空。我们再看看这个EngineModule实例是怎么初始化的:
public CarComponent build() {
if (engineModule == null) {
this.engineModule = new EngineModule();
}
return new DaggerCarComponent(this);
}
在使用Builder构建CarComponent对象的时候,如果为空就创建一个新的实例,并传递给Component对象。同时我们也看到,在Builder中生成了指定module的方法:
public Builder engineModule(EngineModule engineModule) {
this.engineModule = Preconditions.checkNotNull(engineModule);
return this;
}
所以我们可以总结一下,如果我们为AComponent实现XModule的YMethod方法,来声明一个P<Type>,Dagger为我们做了以下的工作来完成这个功能。
- 生成XModule_YMethodFactory.proxyYMethod(XModule instance)的静态代理类和方法,并作为Type实例的获取代理
- 在AComponent和AComponent.Builder中生成XModule的成员,并为XModule成员生成建造模式的初始化方法。
- 在getType()方法里调用第1步生成的代理方法,获取Type实例作为依赖,创建我们要的目标实例。
这个环节完成的依赖模型替换为:
D(DaggerCarComponent,Engine) = D(DaggerCarComponent,EngineModule_ProvideEngineFactory) + D(EngineModule_ProvideEngineFactory, EngineModule) + D(EngineModule,Engine)
这里值得注意的有以下几点:
- 之所以在Builder里生成XModule成员的建造模式方法,是为了提供机制和能力,让我们更加灵活地管理具体的Module,控制对象实例化逻辑。
- D(EngineModule,Engine) 是我们手动实现的,是我们真正的业务逻辑,需要我们多加关注,保证逻辑符合预期。
小结
在这一节中,我们使用module来实现了Engine的提供,并通过源码和逻辑模型,分析了module的工作机制以及其背后的设计原理。但是我们看到,作为Car类的组件,依赖了Engine的Module,稍微有点别扭。有时候我们想控制Component、Module的对应关系,比如为了使我们的代码逻辑更加清晰可读,将这种复用的需求,通过组件的复用来实现。下一篇文章,我们将讲解如何复用Component,敬请期待。
网友评论