Dagger2是编译时依赖注入
框架,也就是说,这个框架解决的问题是,在编译阶段动态生成依赖注入代码,有别于其他依赖注入框架是利用反射或者运行时再动态生成字节码。
通俗点解释就是,a依赖b,但a不控制b的创建和销毁,仅使用b,那么b的控制权交给a之外处理,这叫控制反转(IOC)
,而a要依赖b,必然要使用b的实例,那么有以下几种方法可以拿到b的实例
1)通过a的接口,把b传入;
2)通过a的构造方法,把b传入;
3)通过设置a的属性,把b传入;
这个过程叫依赖注入(DI)
举个栗子:我们在写面向对象程序时,往往会用到组合,即在A类中引用B类,从而可以在A类调用B类的方法完成某些功能,如下所示:
public class ClassA {
...
ClassB b;
...
public ClassA() {
b = new ClassB();
}
public void do() {
...
b.doSomething();
...
}
}
【罪恶根源】这个时候就产生了依赖问题,ClassA依赖于ClassB,必须借助ClassB的方法,才能完成一些功能。这样咋一看,好像并没有什么问题,但是我们在ClassA的构造方法里面直接创建了ClassB的实例,问题就出现在这:
1)在ClassA里直接创建ClassB实例,违背了单一职责原则
,ClassB实例的创建不应由ClassA来完成;
2)其次耦合度增加,扩展性差
,如果需求改变,我们想在实例化ClassB的时候传入参数,那么不得不改动ClassA的构造方法,不符合开闭原则
。
【解决法门】 要解决上述问题,就需要用到依赖注入
,将所需依赖的B类实例注入到宿主A类(或者叫目标类)中,依赖注入有以下几种方式:
1)通过接口注入
interface ClassBInterface {
void setB(ClassB b);
}
public class ClassA implements ClassBInterface {
ClassB classB;
@override
void setB(ClassB b) {
classB = b;
}
}
2)通过构造方法注入
public class ClassA {
ClassB classB;
public void ClassA(ClassB b) {
classB = b;
}
}
3)通过set方法注入
public class ClassA {
ClassB classB;
public void setClassB(ClassB b) {
classB = b;
}
}
从上面可知,随着DI的频繁使用,要实现IOC,会有很多重复代码,甚至随着技术的发展,有更多新的实现方法和方案,那么有人就把这些实现IOC的代码打包成组件或框架,来避免人们重复造轮子,Dagger2就应运而生。
在对应需要用到Dagger2项目的build.gradle中添加以下配置即完成引入,可见源码地址
// Add Dagger dependencies
dependencies {
api 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}
一般项目开发中会使用到Mvp设计模式开发,Presenter会持有View与Model的引用,并且一般需要在View即Activity或者Fragment中new一个Presenter,那么看看使用Dagger2怎么实现
Step 1:先声明对象实例提供类,即用@Module
注解的类,并在该类中用@Provides
注解提供对象实例的方法,@Singleton
注解表示该方法提供的对象是单例,即每次调用该方法获取到的都是第一次初始化的那个对象实例
/**
* @Description:首页对象实例提供器
*/
@Module
public class HomeModule {//类为抽象类的话,那么方法只能是抽象方法,或者静态方法
private Activity mActivity;
public HomeModule(Activity activity){
this.mActivity = activity;
}
/**
* 提供当前的activity上下文环境变量
* @return
*/
@Provides
@ActivityScope
Context provideContext(){
return this.mActivity;
}
}
Step 2:声明依赖注入动作执行类(也叫注射器),即用@Component
注解的类,并在括号中声明依赖注入时,需要用到的对象实例提供类Module,假如依赖的Module方法中有声明Scope,比如:@Singleton
、自定义的@ActivityScope
等,那么@Component
类也需要声明相同的Scope
/**
* @Description:首页dragger注入器
*/
@ActivityScope//依赖的component有设置scope的话,这里也需要设置scope,而且不能跟依赖的scope相同
@Component(modules = {HomeModule.class})
public interface HomeComponent{
//从HomeFragment中查找@Inject的字段,并创建实例
void inject(HomeFragment homeFragment);
@Component.Builder
interface Builder{
@BindsInstance
HomeComponent.Builder setView(HomeFragment homeFragment);
HomeComponent.Builder setHomeModule(HomeModule homeModule);
HomeComponent build();
}
}
Step 3:重新build一下项目,就会在你的项目\build\generated\ap_generated_sources\release\out
自动生成对应的依赖注入代码,规则是Dagger+Component类名
,对于上述例子会自动生成一个HomeComponent接口实现类DaggerHomeComponent
public final class DaggerHomeComponent implements HomeComponent {
private final HomeFragment setView;
private Provider<Context> provideContextProvider;
private DaggerHomeComponent(HomeModule homeModuleParam, HomeFragment setViewParam) {
this.setView = setViewParam;
initialize(homeModuleParam, setViewParam);
}
public static HomeComponent.Builder builder() {
return new Builder();
}
private AppBannerRequestData getAppBannerRequestData() {
return new AppBannerRequestData(provideContextProvider.get());}
private CoreIconRequestData getCoreIconRequestData() {
return new CoreIconRequestData(provideContextProvider.get());}
private CmsRequestData getCmsRequestData() {
return new CmsRequestData(provideContextProvider.get());}
private GameRequestData getGameRequestData() {
return new GameRequestData(provideContextProvider.get());}
private HomePresenter getHomePresenter() {
return injectHomePresenter(HomePresenter_Factory.newInstance(setView, new HomeModel()));}
@SuppressWarnings("unchecked")
private void initialize(final HomeModule homeModuleParam, final HomeFragment setViewParam) {
this.provideContextProvider = DoubleCheck.provider(HomeModule_ProvideContextFactory.create(homeModuleParam));
}
@Override
public void inject(HomeFragment homeFragment) {
injectHomeFragment(homeFragment);}
private HomePresenter injectHomePresenter(HomePresenter instance) {
HomePresenter_MembersInjector.injectMAppBannerRequestData(instance, getAppBannerRequestData());
HomePresenter_MembersInjector.injectMCoreIconRequestData(instance, getCoreIconRequestData());
HomePresenter_MembersInjector.injectMCmsRequestData(instance, getCmsRequestData());
HomePresenter_MembersInjector.injectMGameRequestData(instance, getGameRequestData());
HomePresenter_MembersInjector.injectMGameTitle(instance, new GameTitle());
return instance;
}
private HomeFragment injectHomeFragment(HomeFragment instance) {
BaseFragment_MembersInjector.injectMPresenter(instance, getHomePresenter());
return instance;
}
private static final class Builder implements HomeComponent.Builder {
private HomeFragment setView;
private HomeModule homeModule;
@Override
public Builder setView(HomeFragment homeFragment) {
this.setView = Preconditions.checkNotNull(homeFragment);
return this;
}
@Override
public Builder setHomeModule(HomeModule homeModule) {
this.homeModule = Preconditions.checkNotNull(homeModule);
return this;
}
@Override
public HomeComponent build() {
Preconditions.checkBuilderRequirement(setView, HomeFragment.class);
Preconditions.checkBuilderRequirement(homeModule, HomeModule.class);
return new DaggerHomeComponent(homeModule, setView);
}
}
}
Step 4:最后,在需要进行依赖注入的目标类中,调用自动生成的DaggerHomeComponent,设置Builder对应属性,同时,要是目标类有属性设置@Inject
声明需要依赖注入的话,也需要把目标类传入进去,一般方法名取为inject
,即完成依赖注入
DaggerAppComponent
.builder()
.setAppModule(new AppModule(this))
.setApplication(this)
.build();
上述就是一个简单的且常见的一个Dagger2使用例子,下面我们仔细分析讲解一下上述案例涉及到的概念以及引起的疑问
1、总的来说,Dagger2就是通过依赖注入
完成依赖对象的创建初始化。
这个过程涉及到三个概念:
Dagger2寻找依赖注入的规则如下:
步骤1:查找Module中是否存在创建需要依赖注入类的方法。
步骤2:若存在创建类方法,查看该方法是否存在参数
步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
步骤3:若Module中不存在创建需要依赖注入类的方法,则查找该类中Inject注解的构造函数,看构造函数是否存在参数
步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
常见的专用名词解析:
@Inject
主要有两个作用
@Inject注解构造方法
,此时这个构造方法就作为依赖注入提供方
,作用等同于@Module,但是优先级比@Module低;@Inject注解类成员字段
(注意,不能是private修饰),此时,该类就成为了依赖需求方
,即依赖注入的目标类,那么dagger2就会按照上述规则帮我们实例化该成员类,并注入。使用@Inject可以让IoC容器负责生成instance,如果没有这个注解,dagger将不认识,当做普通类,无法代理
@Module
@Module 注解类,负责创建提供依赖对象实例。
Module 其实是一个简单工厂模式,Module 里面的方法都是创建相应类实例的方法。
到这里,或许你会有疑问,既然使用@Inject注解构造方法就能让Dagger2自动去创建实例,为何还需要有@Module来创建提供实例呢?
原因主要如下:
1)@Inject只能注解自己写的类,要是你的项目依赖到第三方类库
的话,那么就需要@Module注解类中的@Provide方法来创建获得第三方类库的对象。
2) @Inject只能注解一个类中的一个构造方法
,要注解多个会报错,这时就可以借助@Module注解类中的@Provide方法来通过不同的构造方法来创建这个类实例,不过注意,由于多个@Provide方法返回同一个类的实例,那么需要添加@Named声明别名,否则也会报错
@Provides
用于注解@Module 类中的方法
在Module中,我们定义的方法用这个注解,是告诉Dagger2我们想要构造对象并提供这些依赖
@Component
1) @Component一般用来注解接口。
2 )负责在@Inject和@Module之间建立连接。
也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。
3 )实例化目标类中@Inject注解的类成员变量时,遇到没有@Inject注解的构造函数的类依赖,则该依赖由@Module修饰的类提供。
4)一般Component需要引用到目标类的实例,比如上述第一个例子用inject(this)设置进去,这样目标类就和Component建立了联系,Component会去遍历目标类中用Inject注解标注的属性,查找到相应的属性后,会接着查找Module中是否有提供该属性实例的provide方法,没有的话,就去查找该属性对应的用Inject标注的构造函数,剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字:注入器(Injector)
@Scope
@Scope这个注解作用等同于@Singleton,也就是声明对应的依赖注入实例是单例,但是这个单例是指在Component生命周期内唯一,换句话说,就是被自定义@Scope注解之后,也就限制了被标注的实例提供者,只会在Component生命周期内实例化该对象一次,之后每次调用都是返回第一次实例化的对象,除非重新调用一次DaggerXxComponent初始化注入方法才会返回新的
【注意事项】
如果在Module的provideXXX
方法上加上了@Scope
声明,那么在与他关联的Component
上也必须加上相同的@Scope
声明
当Module的provideXXX方法和Component都加上了@Scope
声明,那么在Component实例的生命周期内,只会创建一个由provideXXX方法返回的实例。也就是说,该Component会持有之前通过provideXXX方法创建的实例的引用,如果之前创建过,那么就不再调用Module的provideXXX去创建新的实例,而是直接返回它之前持有的那一份。
在依赖或者继承的组织方式中,如果其中一个Component声明了@Scope
,那么其它的Component也需要声明。
在组织关系中,如果父Component的@Scope
不为@Singleton
,那么子Component的@Scope
可以为@Singleton
。
那么,怎么自定义scope呢?我们来看个示例:
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScope {
}
@Module
public class HomeModule {//类为抽象类的话,那么方法只能是抽象方法,或者静态方法
private Activity mActivity;
public HomeModule(Activity activity){
this.mActivity = activity;
}
/**
* 提供当前的activity上下文环境变量
* @return
*/
@Provides
@FragmentScope
Context provideContext(){
return this.mActivity;
}
}
@FragmentScope//依赖的component有设置scope的话,这里也需要设置scope,而且不能跟依赖component的scope相同
@Component(modules = {HomeModule.class})
public interface HomeComponent{
//从HomeFragment中查找@Inject的字段,并创建实例
void inject(HomeFragment homeFragment);
@Component.Builder
interface Builder{
@BindsInstance
HomeComponent.Builder setView(HomeFragment homeFragment);
HomeComponent.Builder setHomeModule(HomeModule homeModule);
HomeComponent build();
}
}
@Qulifier 与 @Named
我们知道@Module注解类中的Provide方法提供需要以依赖注入的实例对象,但是如果是不同的@Provides方法返回同一类型的对象,Dagger2会选择哪个?
举个例子:provideStudent() 和 provideNewStudent(),返回的都是Student对象。同时写在Module中,会出现什么问题?
@Module
public class MainModule {
@Provides
static Student provideStudent() {
return new Student();
}
@Provides
static Student provideNewStudent() {
return new Student();
}
@Provides
static Teacher provideTeacher() {
return new Teacher();
}
}
这里有两个方法同时返回了Student,我们编译一下,报错了,提示被绑定多次
那么,这时候@Qulifier 与 @Named
就可以派上用场了
1 .@Qulifier
这时候就需要我们提供一个别名,让 目标类成员变量的类型
和 创建方法的返回类型
形成一对一的关系,一般来说,使用@Qulifier
是比较标准的方式。
1.1 定义一个接口学生A和学生B,分别代表两个学生,然后用@Qualifier修饰
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentA{}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentB{}
然后分别在上述Module加上对应注解
@Provides
@StudentA
static Student provideStudent() {
return new Student();
}
@Provides
@StudentB
static Student provideNewStudent() {
return new Student();
}
最后在引用的位置也加上注解,形成一对一的关系
public class MainActivity extends AppCompatActivity {
@Inject
@StudentA
Student student;
@Inject
@StudentB
Student student1;
这样就可以保证,即使是返回相同的类型,也能保证一一对应的关系。
2. @Named
注解
除了上面的@Qualifier
注解,还可以用@Named
注解达到同样的效果
@Provides
@Named("StudentA")
static Student provideStudent() {
return new Student();
}
@Provides
@Named("StudentB")
static Student provideNewStudent() {
return new Student();
}
使用的时候
@Inject
@Named("StudentA")
Student student;
@Inject
@Named("StudentB")
Student student1;
@Binds
@Binds可以理解为关联,首先它是跟@Provides使用位置一样,不同的在于@Provides
注解的方法都是有具体实现
的,而@Binds
修饰的只有方法定义
,并没有具体的实现的,在方法定义中方法参数
必须是 返回值的实现类
。这样创建实体类的地方就不用在Modules 中直接new该对象了,这时,module也需要声明为抽象类,同时其他@Provide注解的方法也需要用static修饰,例如:
@Module
public abstract class AppModule {
@Binds
@Named("AppManager")
abstract IAppManager bindAppManager(AppManager appManager);
@Provides
static Retrofit provideRetrofit() {
return new Retrofit.Builder().build();
}
}
public class AppManager implements IAppManager {
@Inject
Application application;
@Inject
public AppManager() {
}
}
@Component(modules = AppModule.class)
public interface AppComponent {
Application application();
Retrofit retrofit();
void inject(Application application);
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 注入
DaggerAppComponent.builder().application(this).build().inject(this);
}
}
上述例子中,我们还看到了一个新的注解@BindsInstance
,这个注解作用效果等同于下面代码实现,大家可有细品一下
@Module
public abstract class AppModule {
private Application application;
public AppModule(Application application){
this.application = application;
}
@Binds
@Named("AppManager")
abstract IAppManager bindAppManager(AppManager appManager);
@Provides
static Retrofit provideRetrofit() {
return new Retrofit.Builder().build();
}
@Provides
static Retrofit provideApplication() {
return application;
}
}
public class AppManager implements IAppManager {
@Inject
Application application;
@Inject
public AppManager() {
}
}
@Component(modules = AppModule.class)
public interface AppComponent {
Application application();
Retrofit retrofit();
void inject(Application application);
@Component.Builder
interface Builder {
AppComponent build();
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 注入
DaggerAppComponent.builder().application(this).build().inject(this);
}
}
就是假如不在AppComponent 的Builder 中声明@BindInstance
注解的方法,那么就需要在AppModule 中增加以下代码以对外提供Application实例
private Application application;
public AppModule(Application application){
this.application = application;
}
@Provides
static Retrofit provideApplication() {
return application;
}