版权申明】非商业目的可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/80950117
出自:shusheng007
古人曰:纸上得来终觉浅,绝知此事要躬行,这话说的是真他娘的有道理啊,对于编程那点事似乎更是真理。2016年的时候就首次了解过dagger,但是没有去编码实现,当时哥们真觉得懂了,但是最近有个项目要使用Dagger2,突然发现还是不知道如何下手,日了个狗,于是又研究了一番,躬行了一下,总结于此,让那些哭着喊着要求进步的程序员尽快淘汰没有追求的程序员,净化我们的生态环境。
Dagger2 是什么?解决什么问题?具体如何使用?什么原理?如何学习及可否改进?回答的了这些问题说明少年你已经顿悟了,不需要往下看了。
google在dagger2 官网定义如下
Dagger is a fully static, compile-time dependency injection >framework for both Java and Android.
Dagger 是一个可以用于Java及Android平台的依赖注入框架,其注入过程完全是静态的在编译时期完成的(通过编译时产生代码的方式,区别于Spring等框架的依赖反射的方式,反射是基于运行时的)。
Dagger2 主要是通过Java注解(Annotation)来工作的,这一点首先要清楚。如果对注解不是很了解,请移步到 秒懂Java注解。
维基百科上如是说:
In software engineering, dependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.
依赖注入是一种给一个对象提供其依赖的对象的技术。例如A对象需要依赖B对象,那么此处关键点就是不能在A里面去new
B对象,必须通过某种方式将B对象提供给(注入)A中,例如最简单的方式是通过一个set(B b)
方法
当首次接触一些学术名词的时候总是把人虎的一愣一愣的,当了解皮毛后又觉得不过如此,名字叫的到是吓人,再往后通过实践积累了大量相关经验后又感觉这名字叫的真是精妙。例如依赖注入,其实相信有点面向对象高级语言编程经历的人在首次接触这个名称以前都使用过依赖注入,只是自己浑然不知。
例如:下面这种方式中A对B的依赖就不是以依赖注入的方式获得而是A主动构建的B。
public class A {
private B b=new B();
}
而下面这种方式就是将依赖注入到了A类中。
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
那有的同学要问了,这使用依赖注入和不使用依赖注入有什么区别呢?其最主要的目的就是为了解耦,随即而来的就是可测试性,可维护,可扩展性就大大提高了。以上面的情况为例:
第一种方式中A,B紧紧的耦合在了一起。假设由于业务需要 B 的构造函数中多了一个参数,即B的构造方式需要变化,那么我们就必须到A里面去改相关的代码。那只有A一个类使用了B还好,那要是有100个类依赖了B呢?不管你哭不哭,反正王二狗已经哭晕在厕所了。
第二种方式就好很多,因为不需要去修改A类,只需要修改外部生成B对象的地方即可。
使用依赖注入的方式还可以写出便于测试的代码。下面我们通过一个例子看一下:我们可以将A对B的依赖改成依赖B实现了的接口I,如代码所示
public interface I{
void test();
}
public class B implements I{
@Override
public void test() {
System.out.println("总有刁民想害朕");
}
}
public class A {
private I b;
public void setB(I b) {
this.b = b;
}
那样,我们就可以在测试的时候从外面传入一个同样实现了I接口的Mock
类对象,完成测试。
public class Mock implements I{
@Override
public void test() {
System.out.println("这里面可以写测试逻辑");
}
}
第一:dagger 是一个依赖注入框架,首要任务当然是解决依赖注入的问题。
第二:dagger主要想通过编译时产生代码的方式来解决那些基于反射的依赖注入框架所存在的缺点,例如性能问题,开发过程中存在的问题。
在开始使用dagger2之前,首先需要理解它的一些基本概念及工作原理,不然就无从下手。
dagger2中最重要的就是Inject,Component,Module,搞清楚这三个概念基本上可以上手dagger2了.
假设我们有3个类:
DogWang2类代表王二狗,
CuiHuaNiu类代表妹子牛翠花,
ShangGuanNoSnow 是另一个妹子上官无雪。
现在王二狗想让牛翠花给他生猴子,那么就是说王二狗要依赖牛翠花。
//二狗类
public class DogWang2{
@Inject
CuiHuaNiu cuiHua;
@Inject
ShangGuanNoSnow wuXue;
@Inject
public DogWang2(){ }
//此处应该将结果回调,为了简单不写了
public void xxoo(){
System.out.println("翠花,关灯。。。");
cuiHua.birthSon();
}
public void appointment(){
System.out.println("上官无雪,晚上一起去看看月亮聊聊理想啊?");
wuXue.turnDown();
}
}
//翠花类
public class CuiHuaNiu{
@Inject
public CuiHuaNiu(){}
public void birthSon(){
System.out.println("我王二狗的儿子王不二降临人世了!");
}
}
那上面的代码是如何起作用的的呢?关键就看下面这3个注解了。
虽然王二狗和牛翠花20多年前就定了娃娃亲,被配成了一对,产生了联系(通过 @Inject
标记),但是王二狗在天津,牛翠花在上海,这两个人也没见过面,没法真的在一起生猴子。那就需要一个两人都认识的中间人,将他们二人撮合到一起,这个中间人就是王婆Component。在一个月黑风高的夜晚王二狗看了一部日本爱情动作片后打开了王婆的微信,“王姐啊,你能不能帮我联系一下我那从未见过面的未婚妻,我想和她见个面,事成之后请你吃饭”。王婆于是打开微信,找到牛翠花,让她周末来天津和王二狗见面。映射到编程领域如下:
Component 一般是一个使用 @Component标记的接口,其持有DogWang2类的实例,其在DogWang2类中发现有使用@Inject
标记的属性cuiHua,就会去查找这个属性的类型CuiHuaNiu,然后调用其构造函数产生实例对象赋值给cuiHua。这样就完成了对DogWang2类中cuiHua属性的赋值,即完成了依赖注入。
按理说有了上面两步就可以完成依赖注入了,那为什么还要一个Module
,这是为“第三者”准备的。
例如王二狗这厮突然看上了新来的测试妹子上官无雪 ShangGuanNoSnow类,找王婆搭线。王婆说不行啊,人家设置了不接受陌生人撩拨(没有使用@Inject
标注自己的构造函数),二狗贼心不死,央求王婆,王婆于是找到了上官无雪的闺蜜二丫,二丫把上官无雪的联系方式告诉了王婆,于是王婆。。。二丫就是一个Module
。映射到编程领域如下:
ShangGuanNoSnow类是一个没有使用@Inject
注解其构造函数的类,而且我们也无法加上,可能由于它是一个第三方类,或者其他原因。那我们想要使用dagger2将其注入到DogWang2类中就需要使用到Module
。Module
负责提供ShangGuanNoSnow类的实例对象,当Component发现了DogWang2类中使用 @Inject
注解的ShangGuanNoSnow类属性时,先去Module中查找,没有的话再去看其构造函数是不是使用@Inject
标记了,如果都没有,注入失败。
先看下项目结构,尽量简单了。
其中A类和B类已经在上面列出了。其中 A 类为DogWang2,B类为CuiHuaNiu,C类为ShangGuanNoSnow
具体步骤如下
1: 先定位自己要使用的类,决定是以在此类构造函数上添加@Inject
呢,还是在Module
中提供@providers
方法的方式来使用注入。例如我们的C类,我们使用第二种方式。
public class C{
public void turnDown(){
System.out.println("癞蛤蟆想吃天鹅肉,滚!");
}
}
2 : 定义Module
,如果使用第一种方式,可以没有Module
,此步骤可以省略.
@Module
public class DaggerModule{
//此处为了尽量简单,其实这边可以有很多种写法
@Provides
public C providerC(){
return new C();
}
}
3:定义Component
,就是一个使用@Component
标记的一个接口,把这个Component
要用到的Module
以modules = {Module1.class,Module2.class}
的形式提供出来,例如下面的代码中,MatchComponent
依赖了一个DaggerModule
。
@Component(modules = {DaggerModule.class})
public interface MatchComponent{
void mainActivityInject(MainActivity activity);
}
上面的代码中之所以注入的是MainActivity
,是因为我们讲的是如何在Android中使用,所以我要在MainActivity
中注入对A类的依赖。那样我只要在MainActivity
中申明 @Inject A a;
,dagger就会帮我注入A的实例对象.
4:客户端中使用,例如我们的MainActivity
。将要注入的对象使用@Inject
标记,使用dagger为我们生成的Component
的实例调用里面的注入方法void mainActivityInject(MainActivity activity);
。大功告成,瞬间秒懂。
public class MainActivity extends AppCompatActivity{
@Inject
A a;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MatchComponent component= DaggerMatchComponent
.builder()
.build();
component.mainActivityInject(this);
findViewById(R.id.btn_niu).setOnClickListener(v->{a.xxoo();});
findViewById(R.id.btn_xue).setOnClickListener(v->{a.appointment();});
}
}
不知道大家会不会有疑问,我们要注入的是C类对象,而在客户端注入的是A类对象,那C类对象是怎么注入的呢?仔细看一下A类的代码就明白了,这个是一个递归注入的过程。
翠花,关灯。。。
我王二狗的儿子王不二降临人世了!
如果我们点击第二个按钮,系统就会输出:
上官无雪,晚上一起去看看月亮聊聊理想啊?
癞蛤蟆想吃天鹅肉,滚!
dagger2之所以这么神奇,还是因为注解的力量,强烈推荐去看这篇文章秒懂Java注解,就是因为dagger2在背后默默的为我们生成了很多代码,才使得我们使用如此轻松。看一下dagger2为我们生产的代码。
那么我们如何阅读这些生成的代码呢?如果没有一个好的方法,就会感觉无从下手,一团乱麻。
首先到我们的MainActivity
中,找到注入的这句话,这是在Component
中申明的方法component.mainActivityInject(this);
,点击Ctr+Alt+B 就会定位到那个具体实现了Component
接口的类DaggerMatchComponent
中。
@Override
public void mainActivityInject(MainActivity activity) {
injectMainActivity(activity);
}
接着看injectMainActivity(activity)
方法,看到其调用了MainActivity_MembersInjector
类的injectA()
方法
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectA(instance, getA());
return instance;
}
而MainActivity_MembersInjector
类的injectA()
方法很简单,就是讲传入的a
实例对象赋值给了MainActivity
中我们声明的a
变量,现在关键就是看这个传入的a实例对象如何产生的了。
public static void injectA(MainActivity instance, A a) {
instance.a = a;
}
我们看到这个a实例对象是由DaggerMatchComponent类中的getA()
方法产生的。
private A getA() {
return injectA(A_Factory.newA());
}
private A injectA(A instance) {
A_MembersInjector.injectB(instance, new B());
A_MembersInjector.injectC(instance, DaggerModule_ProviderCFactory.proxyProviderC(daggerModule));
return instance;
}
可以从上面的代码看到,a实例对象最终是由A_Factory
类的newA()
方法产生的
public static A newA() {
return new A();
}
至此,我们已经明白了dagger是如何为我们在MainActivity
中的A 属性注入实例对象的了。
而在注入A的过程中,dagger2还注入了B和C的对象,具体代码在上面injectA(A instance)
方法里面,有兴趣的同学可以查看其源代码,也非常简单。
A_MembersInjector.injectB(instance, new B());
A_MembersInjector.injectC(instance, DaggerModule_ProviderCFactory.proxyProviderC(daggerModule));
这里只是希望可以让对dagger2一知半解的同学快速入门,dagger2还有许多高级的特性等待你们去挖掘,祝愿编码愉快,生活愉快。
也希望王二狗可以珍惜眼前人,抛弃令人唾弃的肮脏想法,和牛翠花愉快的培养祖国的下一代。
源代码下载地址