依赖注入框架Dagger2详解(一),依赖注入和控制反转的深入理解
依赖注入框架Dagger2详解(二),Java依赖注入标准JSR-330规范
依赖注入框架Dagger2详解(三),Java注解处理器APT入门
依赖注入框架Dagger2详解(四),初级篇
依赖注入框架Dagger2详解(五),中级篇
依赖注入框架Dagger2详解(六),高级篇
尽管Dagger2看起来很容易,但其实里面各种细节很值得注意,这一篇我们将学习它的一些进阶用法。
Module中@Provides方法可以带输入参数,其参数由Module集合中的其他@Provides方法提供,或者自动调用构造方法
下面是其他@Provides方法提供的例子
@Module
public class FruitModule{
//8输入参数自动使用到provideFruit()的返回值Color.RED
@Provides
public Fruit provideFruit(Color color){
return new Apple(color,Size.BIG);
}
@Provides
pulic Color provideFruit(){
return Color.RED;
}
}
如果找不到@Provides方法提供对应参数的对象,自动调用带@Inject参数的构造方法生成相应对象
@Module
public class FruitModule{
@Provides
public Fruit provideFruit(FruitInfo info){//自动查找到FruitInfo中带@Inject的无参构造器并生成实例传入参数info
return new Apple(info);
}
}
public class FruitInfo{
Color mColor;
Size mSize;
@Inject
FruitInfo(){
mColor=Color.RED;
mSize=Size.BIG;
}
}
当有Fruit需要注入时,Dagger2就会在Module中查找返回类型为Fruit的方法,也就是说,Dagger2是按照Provide方法返回类型查找对应的依赖。但是,当Container需要依赖两种不同的Fruit时,你就需要写两个@Provides方法,而且这两个@Provides方法都是返回Fruit类型,靠判别返回值的做法就行不通了。这就需要使用@Named来区分,如下:
//定义Module
@Module
public class FruitModule{
@Named("typeA")
@Provides
public Fruit provideApple(){ //提供Apple给对应的mFruitA
return new Apple();
}
@Named("typeB")
@Provides
public Fruit provdeBanana(){ //提供Banana给对应的mFruitB
return new Banana()
}
}
//定义Component
@Component(modules={FruitModule.class})
interface FruitComponent{ //Dagger根据接口自动生成FruitComponent
void inject(Container container);
}
//定义Container
class Container{
@Named("typeA") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的元依赖 @Inject
Fruit mFruitA;
@Named("typeB") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的依赖 @Inject
Fruit mFruitB;
...
public void init(){
DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的实现类注入
}
}
这样,只有相同的@Named的@Inject成员变量与@Provides方法才可以被对应起来。
如果觉得@Named只能用字符串区分不满足需求,你也可以自定义类似@Named的注解,使用元注解@Qualifier可以实现这种注解,比如实现一个用int类型区分的@IntNamed
@Qualifier //必须,表示IntNamed是用来做区分用途
@Documented //规范要求是Documented,当然不写也问题不大,但是建议写,做提示作用
@Retention(RetentionPolicy.RUNTIME) //规范要求是Runtime级别
public @interface IntNamed{
int value();
}
接下来使用我们定义的@IntNamed来修改上面FruitA,FruitB的例子如下
//定义Module
@Module
class FruitModule{
@IntName(1)
@Provides
public Fruit provideApple(){ //提供Apple给对应的mFruitA
return new Apple();
}
@IntName(2)
@Provides
public Fruit provdeBanana(){ //提供Banana给对应的mFruitB
return new Banana()
}
}
//定义Component
@Component(modules={FruitModule.class})
interface FruitComponent{ //Dagger根据接口自动生成FruitComponent
void inject(Container container);
}
//定义Container
class Container{
@IntName(1) //添加标记@IntName(1),只获取对应的@IntName(1)的元依赖
@Inject
Fruit mFruitA;
@IntName(2) //添加标记@IntName(2),只获取对应的@IntName(2)的依赖
@Inject
Fruit mFruitB;
...
public void init(){
DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的实现类注入
}
}
一个Component可以包含多个Module,这样Component获取依赖时候会自动从多个Module中查找获取,Module间不能有重复方法。添加多个Module有两种方法,一种是在Component的注解@Component(modules={××××,×××}) 添加多个modules,如下
@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) //添加多个Module
public interface FruitComponent{
...
}
另外一种添加多个Module的方法可以被使用Module中的@Module(includes={××××,×××}),如下
@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
public class FruitModule{
...
}
@Component(modules={FruitModule.class}) //添加多个Module
public interface FruitComponent{
...
}
这种使用Module的includes的方法一般用于构建更高层的Module时候使用。
上面简单例子中,当调用DaggerFruitComponent.create()实际上等价于DaggerFruitComponent.builder().build()。可以看出,DaggerFruitComponent使用了构造者模式。在构建的过程中,默认使用Module无参构造器产生实例。如果需要传入特定的Module实例,可以使用
DaggerFruitComponent.builder()
.moduleA(new ModuleA()) //指定Module实例
.moduleB(new ModuleB())
.build()
如果Module只有有参构造器,则必须显式传入Module实例。
//定义ComponentB
@Component(modules={××××××××})//1.假设Module中没有provideApp()方法,但有provideInfo()
interface ComponentB{
Apple apple(); //2.实现类自动返回由Apple(info)构建的实现类
}
public class Apple{
@Inject
Apple(Info info){//被@Inject标记,使用这个构造器生成实例
...
}
Apple(){ //不会使用这个构造器,没有被@Inject标记
}
}
上述代码会生成ComponentB的实现类DaggerComponetB,调用其apple()方法会自动使用Apple(info)构造器生成实例返回。
//定义ComponentB
@Component(modules={××××××××})
interface ComponentB{
...
}
//定义ComponentA
@Component(dependencies={ComponentB.class},modules={××××××××})//使用dependencies
interface ComponentA{
...
}
这样,当使用ComponentA注入Container时,如果找不到对应的依赖,就会到ComponentB中查找。但是,ComponentB必须显式把这些A找不到的依赖提供给A。怎么提供呢,只需要在ComponentB中添加方法即可,如下
@Component(modules={××××××××})
interface ComponentB{
// 假设A中module中找不到apple,banana,oranges,但是B的module有,B必须提供带返回值的方法如下
Apple apple();
Banana banana();
Oranges oranges();
}