几个问题
看到Dagger2这个词的时候,相信很多人会有很多的疑问如下:Dagger2如何使用在例子中?Inject,Component,Module,Container、Provides它们是什么概念?怎么去理解它们?各自有什么作用?
首先看一张图,解释了Dagger2它必不可少的三个元素。建议看完文章后回来再看。
然后总结几个很重要的“单词”(可以先读个大概,文章底部针对这里有更形象的总结,看完文章再回来看肯定就明白了)
@Inject
通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。 官方点说就是带有此注解的属性或构造方法将参与到依赖注入中,Dagger2会实例化有此注解的类。
@Module
Modules类里面的方法专门用来提供依赖,他就像一个工厂一样去生产需要添加依赖的实例。所以我们定义一个类,用@Module来注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。modules的一个重要特性是它们设计为分区并组合在一起(例如,我们的app中可以有多个组成在一起的modules)。它里面定义一些用@Provides注解的以provide开头的方法,这些方法就是所提供的依赖,Dagger2会在该类中寻找实例化某个类所需要的依赖。
@Provides
上面引入了这个概念了。在modules中,我们定义的方法是用@Provides这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
@Component
Components从根本上来说他就是一个注入器,也可以说是用来将@Inject和@Module联系起来的桥梁,它的主要作用就是连接这两个部分。Components可以提供所有定义了的类型的实例(inject需要),比如:我们必须用@Component注解一个接口然后列出所有的 。功能是从@Module中获取依赖并将依赖注入给@Inject
那么接下来就看看如何使用,还是使用第一篇的例子且先写的简单点,把此作为入门案例。
使用Dagger2
准备工作:
- 配置apt插件(在build.gradle(Project:xxx)中添加如下代码)
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
//添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
- 添加依赖(在build.gradle(Module:app)中添加如下代码)
apply plugin: 'com.android.application'
//添加如下代码,应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
//java注解
compile 'org.glassfish:javax.annotation:10.0-b28'
...
}
上一篇的代码修改如下:
1、在需要引入依赖对象的类中加入@Inject注解
public class MainActivity extends AppCompatActivity {
@Inject
ApiServer mApiServer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mApiServer.register();
}
}
以前是通过new ApiServer();获取这个实例的对象的吧。引入注解概念之后,不需要new对象了,直接通过
@Inject
ApiServer mApiServer;
的方式。然后找到加注解的依赖类的构造方法:
public class ApiServer {
@Inject
public ApiServer(){
Log.e("Howard","ApiServer--->start");
}
/**
* 往服务端保存用户信息
*/
public void register() {
Log.e("Howard","ApiServer--->register");
}
}
这样我们就可以让目标类MainActivity中所依赖的其他类ApiServer,与其他类ApiServer的构造函数之间有了一种无形的映射联系。
这个时候Dagger2就会去找这个类的实例,文章开头也说了Components是一个桥梁,Dagger2会到中介Components中去找这个实例的(其实是借助Compoent找,他类似中介)。
所以要创建一个Component,概念也说了:我们必须用@Component注解一个接口然后列出所有的 ,他提供的是一系列的接口。例如这里通过如下方式:
2、创建Components对应上面的Component
@Component
public interface UserCompoent {
void inject(MainActivity mainActivity);
}
注意Component是需要加@Component来注解的,这样系统才知道Component层Who && Where。void inject(MainActivity mainActivity);目的是让Component与Container(这里是MainActivity)发生关系的,因为Component需要引用到目标类(这里是MainActivity)的实例。
这样加入这个Component之后,大致流程如下:
Component会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字注入器(Injector)
这个时候,我们还没有把目标类(这里是MainActivity)给Component,还需要做一步工作:
(需要Builder以下工程)
在MainActivity的onCreate()方法中:
DaggerUserComponet.create().inject(this);
运行程序看log日志:
哇!始终都没有创建ApiServer的实例吧,它就能调用该依赖类的方法啦!
此时你会产生疑问吧,不是开头图片给了三个重要的“属性”吗?怎么没有引入Modules层就运行成功了?那Modules层存在意义又如何?,带着疑问一一揭开谜团
开头说了,MainModlue是一个注解类,用@Module注解标注,主要用来提供依赖。之所以有Module类主要是为了提供那些没有构造函数的类的依赖,这些类无法用@Inject标注,比如第三方类库,系统类等等,当然这句话并不是绝对的,其实Module在任何场景都可以加入并不一定非要在引入系统类以及第三方库的时候,包括上面的简单案例,都可以引入一个Module(基本都需要写Modules)。
让我们看看怎么写:
3、创建Modules层对应上面的Module,创建Provides提供依赖对象
@Module
public class UserModule {
@Provides
public ApiServer provideApiServer(){
Log.e("Howard","provideApiServer--->start");
return new ApiServer();
}
}
这里创建了UserModule 类,开始还说了在modules中,我们定义的方法是用@Provides这个注解,以此来告诉Dagger2我们想要构造对象并提供对应的依赖。这里创建provideApiServer()方法,通过@Provides注解,然后创建mApiServer依赖实例返回出去。
那么就这么创建了一个类有啥意义吗?没有意义,这个类显然是独立的,跟任何类没有半点关系。前面说到,Component是一个桥梁,而真正提供依赖的是Modules中,对应这个mApiServer实例应该由Modules那边给出。它应该与Component层产生点关系建立某种链接:
修改Component层的代码:
@Component(modules = UserModule.class)
public interface UserCompoent {
void inject(MainActivity mainActivity);
}
@Component(modules = UserModule.class)一行代码就把Component与Modules建立了连接通道。这样运行程序:
看Log的执行流程也可以知道:因为在Activity中需要一个mApiServer实例,先去UserModule里面的provideApiServer()拿到了这个实例,在这里走ApiServer的构造方法,创建出依赖的实例,再回到MainActivity中调用了register();方法。创建实例的工作,已经帮咱们隐形中创建好了!
注意一个细节,还记得开始咱们说的加入@Inject注解吗,需要在对应属性的构造方法上也加上注解,咱们引入了Module后,即使是去掉依赖构造方法上的@Inject也是没有任何问题的这个可以自行测试一下。
完了上面,我们再来总结一下:
在Activity中(就是一个Container),需要一个依赖,也就是说需要一个实例他叫mApiServer,我们想把所有的工作给Dagger2来提供这个实例。
首先要告知Dagger2我们需要依赖,通过@Inject告知后,Dagger2知道我们的Activity需要这个实例,那么Dagger2就会去Components找(其实不是去Components找,是Components帮我们找),比如上面@Component(modules = UserModule.class),他只是一个中介罢了他不提供实例,看到是通过UserModule(Modules)来提供的,Modules里面会有很多方法都是提供不同的依赖实例的,他在这里面找到被@Provides注解的方法,获取到这个实例。@Component(modules = UserModule.class)这句话已经是把Components层与Modules层关联了起来了,而且这里的modules还可以继续添加用逗号分开:@Component(modules = UserModule.class,在此添加Module层Clazz)。然后Components与Container的关联通过类似如下代码:
void inject(MainActivity mainActivity);关联起来的。一个细节在于,如果没有引入Modules层,Container与依赖类之间产生映射关系必须在依赖类的构造方法上加上@Inject注解;如果引入了Modules层,依赖类的构造方法不需要加上@Inject注解也是可以的。
接下来再举一个例子:
这次使用UserManager实例,依赖这个实例。
在MainActivity中:
public class MainActivity extends AppCompatActivity {
@Inject
UserManager mUserManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
我们需要mUserManager这个实例,不可以直接new对象。这里要注意的是,使用@Inject时,不能用private修饰符修饰类的成员属性。
还记的上面的步骤吗?
Components层的写法跟上面一样。
在Modules层:
@Module
public class UserModule {
// @Provides
// public ApiServer provideApiServer(){
// return new ApiServer();
// }
@Provides
public UserManager provideUserManager(ApiServer mApiServer){
return new UserManager(mApiServer);
}
}
因为new UserManager实例需要传入一个ApiServer 类型的参数,所以在provideUserManager()里面传入对应的参数即可。
然后在MainActivity中使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerUserComponet.create().inject(this);
mUserManager.register();
}
运行程序:
发现崩溃、崩溃的原因在于,在Module中的provideUserManager()方法需要参数它是一个对象,而我们并没有在哪里实例化过这个对象参数给他,所以报错。
因此需要提供对应的参数实例。有两种方式提供对应的实例:
方式一
直接在UserModule 类中提供如下方法:
@Provides
public ApiServer provideApiServer(){
Log.e("Howard","provideApiServer--->start");
return new ApiServer();
}
此时运行程序,运行成功,看log日志:
按照上面解释的流程,相信这个log结果是理所当然的。
方法二
直接对ApiServer 的构造方法加上Inject
@Inject
public ApiServer(){
Log.e("Howard","ApiServer--->start");
}
运行结果:
再举一个例子:
在Activity中:
public class MainActivity extends AppCompatActivity {
@Inject
UserManager mUserManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUserManager.register();
}
}
对于UserManager:
public class UserManager {
private ApiServer mApiServer;
private UserStor mUserStor;
public UserManager(ApiServer mApiServer,UserStor userStor){
this.mApiServer = mApiServer;
this.mUserStor = userStor;
}
/**
* 管理用户注册
*/
public void register() {
Log.e("Howard","UserManager--->register");
mApiServer.register();
mUserStor.register();
}
}
然后Components层的写法跟上面一样。
然后Module类:
@Module
public class UserModule {
private Context mContext;
public UserModule(Context context){
mContext = context;
}
@Provides
public ApiServer provideApiServer(){
Log.e("Howard","provideApiServer--->start");
return new ApiServer();
}
@Provides
public UserStor provideUserStor(){
return new UserStor(mContext);
}
@Provides
public UserManager provideUserManager(ApiServer mApiServer,UserStor userStor){
Log.e("Howard","provideUserManager--->start");
return new UserManager(mApiServer,userStor);
}
}
这里主要讲一下变化的地方:
@Provides
public UserStor provideUserStor(){
return new UserStor(mContext);
}
UserStor需要传入上下文的参数,因此需要提供一个上下文参数。这里通过构造方法:
private Context mContext;
public UserModule(Context context){
mContext = context;
}
此时在MainActivity中需要加入如下代码:
DaggerUserComponet.builder()
.userModule(new UserModule(getApplicationContext()))
.build()
.inject(this);
此时不可以直接跟第二个实例那样
DaggerUserComponet.create().inject(this);
了。因为此时你会发现DaggerUserComponet中压根没有create()方法了。而且UserModule需要传入参数,必须通过上面方式new UserModule(getApplicationContext())才能传入需要的上下文对象。
然后接下来是对前两篇文章的一个总结。
总结
依赖注入:就是目标类(目标类需要进行依赖初始化的类,下面都会用目标类一词来指代)中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建,而是通过技术手段可以把其他的类的已经初始化好的实例自动注入到目标类中。而dagger2就是实现依赖注入的一种技术手段。
Inject是什么
我们可以用注解(Annotation)来标注目标类中所依赖的其他类,同样可以用来注解来标注所依赖的其他类的构造函数(注意:只要是提供了Module,不再依赖的构造方法中加入@Inject也是可以的,如果不加入Module,要想目标跟依赖产生映射关系,必须加入@Inject),那注解的名字就叫Inject
class A{
@Inject
B b;
}
class B{
@Inject
B(){
}
}
这样我们就可以让目标类中所依赖的其他类与其他类的构造函数之间有了一种无形的映射联系。但是要想使它们之间产生直接的关系,还得需要一个桥梁来把它们之间连接起来。那这个桥梁就是Component了。
Component又是什么
Component也是一个注解类,一个类要想是Component,必须用Component注解来标注该类,并且该类是接口或抽象类。上文中提到Component在目标类中所依赖的其他类与其他类的构造函数之间可以起到一个桥梁的作用。
那我们看看这桥梁是怎么工作的:
Component需要引用到目标类的实例,Component会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了,如果是第三方库或者系统库,没有用@Inject标注这些构造函数就需要提供下面的Modules),剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字注入器(Injector)
目标类想要初始化自己依赖的其他类:
- 用Inject注解标注目标类中其他类
- 用Inject注解标注其他类的构造函数
- 若其他类还依赖于其他的类,则重复进行上面2个步骤
- 调用Component(注入器)的injectXXX(Object)方法开始注入(injectXXX方法名字是官方推荐的名字,以inject开始)
Component现在是一个注入器,就像注射器一样,Component会把目标类依赖的实例注入到目标类中,来初始化目标类中的依赖。
为啥又造出个Module
现在有个新问题:项目中使用到了第三方的类库,第三方类库又不能修改,所以根本不可能把Inject注解加入这些类中,这时我们的Inject就失效了。
那我们可以封装第三方的类库,封装的代码怎么管理呢,总不能让这些封装的代码散落在项目中的任何地方,总得有个好的管理机制,那Module就可以担当此任。
可以把封装第三方类库的代码放入Module中,像下面的例子:
@Module
public class ModuleClass{
//A是第三方类库中的一个类
A provideA(){
return A();
}
}
,Module就像是一个简单工厂模式,Module里面的方法基本都是创建类实例的方法。接下来问题来了,因为Component是注入器(Injector),我们怎么能让Component与Module有联系呢?
Component增加的职责
Component是注入器,它一端连接目标类,另一端连接目标类依赖实例,它把目标类依赖实例注入到目标类中。上文中的Module是一个提供类实例的类,所以Module应该是属于Component的实例端的(连接各种目标类依赖实例的端),Component的新职责就是管理好Module,Component中的modules属性可以把Module加入Component,modules可以加入多个Module(注意这句话)。
那接下来的问题是怎么把Module中的各种创建类的实例方法与目标类中的用Inject注解标注的依赖产生关联,那Provides注解就该登场了。
Provides最终解决第三方类库依赖注入问题
Module中的创建类实例方法用Provides进行标注,Component在搜索到目标类中用Inject注解标注的属性后,Component就会去Module中去查找用Provides标注的对应的创建类实例方法,这样就可以解决第三方类库用dagger2实现依赖注入了。
Inject,Component,Module,Provides是dagger2中的最基础最核心的知识点。奠定了dagger2的整个依赖注入框架。
- Inject主要是用来标注目标类的依赖和依赖的构造函数
- Component它是一个桥梁,一端是目标类,另一端是目标类所依赖类的实例,它也是注入器(Injector)负责把目标类所依赖类的实例注入到目标类中,同时它也管理Module。
- Module和Provides,Module类似是一个简单工厂模式,Module可以包含创建类实例的方法,这些方法用Provides来标注
到目前为止,通过这两篇文章,了解了什么是Dagger2以及四层抽象概念、以及如何简单使用Dagger2 。
参考博客:
http://www.jianshu.com/p/39d1df6c877d
http://www.jianshu.com/p/cd2c1c9f68d4
http://blog.csdn.net/lisdye2/article/details/51942511#comments