Dhroid框架六大组件之Ioc容器【上】
2016-05-17 20:53
435 查看
本文,是假设你有使用或了解过dhroid框架的基础上进行讲解的,如果对该框架并不了解,但想学习的,可以先到这里去学习一下:http://www.oschina.net/p/dhroid。 这里有提供下载地址和简单教程。努力深入学习别人的框架,看看别人是如何从0到1做出一个框架的,当你有能力优化,乃至创建一个新的、更优的框架的时候,你就是下一个大神了,距离升职加薪,迎取白富美,又跨进了一大步!就算迎取不到白富美,至少加薪应该不成问题,有了钱,大宝健杠杠的,大宝健上,要啥白富美没有,是吧!!!好了,不扯蛋了。让我们开始进入主题,走向通往大神之路吧。
dhroid框架的六大组件之一的Ioc容器,他的功能,官方描述起来,很抽象:“视图注入,对象注入,接口注入,解决类依赖关系”。现在,我开始一步一步的解剖Ioc,尽量更简单化的将其描述出来。
dhroid框架,在使用之前,必须在app的启动类里进行初始化,这个类必须是application的子类。
通过,查看源代码,可以知道init的实现,如下(此处只讲Ioc,图片和DB等无关的,不列出代码):
咱们,先继续纵深,了解initApplication(app)这个方法:
咱们,再继续纵深。。。这里是多包了一层了:
这个方法是在一个叫做IocContainer的类里面的,用于全局存放这个application的子类对象。毕竟,这个类有大用处。后面再慢慢说。到此为止,就已经了解了框架的第一步初始化代码,竟然仅仅只是保存一个application子类的对象而已。然后,我们再继续讨论,init方法的第二行代码,因为这行代码有多个方法,咱们一个一个来解剖,先看bind。
这行代码,从bind这个单词就可以见名知义,即是将这个类捆绑起来。捆绑起来做啥?就是用来做单例。。。这样以后,你再想引用这个单例类(哪怕这个类本身并无单例模式的实现方式)。知道作用后,咱们再,继续解剖bind的源码:
下一层bind类的具体实现。
这里通过给Instance构造函数传一个Class对象,来构造一个Instance实例。说到这里,又得再先深入Instance类的实现,不知道看客你的递归思维如何,这一层套一层的,或许可以先讲功能,再解剖这个类,但我还是比较喜欢一层一层纵深解剖,这样才不会出现有些代码看后出现,一知半解的情况。但是Instance类过于庞大,咱们这里先讲用到的,看到的,那就先从Instance的构造函数来说。
没错…就是这么简单。来,咱们继续看下一行代码,这行代码,简化着来看就是 instance.setAsAlians(new AsAlians());但这里的AsAlians类是一个接口,这里采用匿名类的写法,并通过重写AsAlians类里的方法,来实例化一个AsAlians对象。上面看到的匿名AsAlians的实现代码的触发,是在另一个地方,这里看不看都无所谓,只要知道Instance类里有一个Aslians变量,而且这个变量的as(Instance ins, String name, Class toClazz)方法的具体实现,是在这里就行。
那么,即然这个Bind方法的最复杂的部分,在这里不需要看,那我们就“先”continue,看下一行代码:return instance,噢,没了。。。这个bind方法到此就没了。
那我们来总结一下,这行代码做了啥事:
1、将SimpleValueFix类,传给Instance类的构造方法,来实例化一个带有SimpleValueFix类变量(这个变量是Class类,所以,可以赋值所有xxx.class类)的Instance类。
2、给Instance类的实例,赋值一个有具体实现的AsAlians接口类对象。
3、返回一个初始化过的Instance类对象。
4、总结,其实,就是绑定了一个具体的类。
好了,到此,我们就已经知道了bind方法做了什么事。接下来,继续看下一行代码,to方法的实现:
这个to方法,是用来将一个类,注入到bind方法中已绑定的类里面。现在,就得review一下前面as方法的具体实现代码了!
如果name不为空。因为从name方法里面,可以看到as方法,只传name,是因为这里是通过以name为key,来绑定对应的instance对象。而to方法里面,可以看到as方法,只传Class,是因为这里通过以Class为key,来绑定对应的instance对象。到此,对于bind和to方法,我们已经够明白了。
最后,我们来讨论一下最后一个方法,scope方法的实现:
下面是scope方法的具体实现:
从注释可以看出来,这个方法是用来设置作用域的。那么,问题就来了:
1、是谁的作用域?
2、一共有哪些作用域?
为了解答这两个问题,我们有必要继续看一下Instance类的其它代码段:
看完上面的代码后,答案恐怕大家都知道了:
一、 成员变量obj的作用域。
二、作用域有两种:
1、单例,即整个应用只保存一个对象,只要对象存在,就不再创建。(具体可以参考,设计模式之单例模式,但这里,是通过对某一个类的对象的保存来实现单例。即先判断类,如果该类有已存在的对象,就返回该对象。否则,才创建新的)
2、每次都创建。。这个不需要解释了,与new没啥区别了。
这里,我们就只讲解单例模式,即scope == InstanceScope.SCOPE_SINGLETON的情况。由代码,可知,框架首先会先判断当前的obj对象是为空,如果为空就执行一个叫做bulidObj的方法来创建一个obj对象。如果已存在,就直接返回这个obj对象来实现单例效果。那么,问题来了,这个buildObj究竟是创建哪个类的对象呢?为了答案,我们就得继续来解剖一下bulidObj(context)方法,看他是如何创建一个对象的。
这段代码,如果你是大牛,那可以一口气看完,如果跟我一样是小渣渣的话,咱们不妨把这个方法,拆分成两部分,化繁为简,由易入难。先看第一部分:
首先,是定义两个临时变量,一个Object ,一个Constructor。然后,再判断这个context是否为空,如果不非为空,就通过claszz类的getDeclaredConstructors()方法,来获取claszz类的所有构造方法。然后,再判断某个类里面的所有构造方法里面有没有一个构造方法,只需要传一个context传数,就能创建实例的,如果有,就将这个构造方法赋值给Constructor类的对象。如果没有,那么Contructor类的对象则为null。好了,上部分代码就是这么简单,下面我们继续看第二部分。
如果construstor不为空,即存在一个只需一个context参数就能创建实例的构造方法时,就用这个构造方法来创建实例。如果没有,就通过调用Class的newInstance();来创建实例。有人就想问了,那创建的是哪个类的实例呢?为了回答这个问题,我们就得看看这个claszz对象是由谁给赋的值了。细心和记忆力好的朋友,可能就已经发现了,在本文的前段,有这样的代码,这里再贴出来,免得大家还得往上拉去看看。
这里的Instance的clazz参数是的bind方法的clazz参数,直接传递赋值的。所以,你bind传了一个什么类,那么这里clazz.newInstance()就会创建出一个什么类对象来。
到此为止,我们就已经完整的分析了Ioc容器的初始化代码
但是,我们平时使用Ioc的时候,都不是Ioc.bind.to.scope这样使用,而是直接通过下面的代码,来创建一个类对象的。
那么,我们就有必要,继续来解剖Ioc的get方法,来看看这个get方法是做啥的,又与前面的Ioc初始化有毛关系。但限于篇幅,剩下的我们就在“Dhroid框架六大组件之Ioc容器【下】”来详细解剖和分析,坚持下去,Ioc容器剩下的内容并不多了。
dhroid框架的六大组件之一的Ioc容器,他的功能,官方描述起来,很抽象:“视图注入,对象注入,接口注入,解决类依赖关系”。现在,我开始一步一步的解剖Ioc,尽量更简单化的将其描述出来。
dhroid框架,在使用之前,必须在app的启动类里进行初始化,这个类必须是application的子类。
Dhroid.init(this); //Dhroid初始化
通过,查看源代码,可以知道init的实现,如下(此处只讲Ioc,图片和DB等无关的,不列出代码):
Ioc.initApplication(app); Ioc.bind(SimpleValueFix.class) .to(ValueFix.class).scope(InstanceScope.SCOPE_SINGLETON);
咱们,先继续纵深,了解initApplication(app)这个方法:
public static void initApplication(Application application) { IocContainer.getShare().initApplication(application); }
咱们,再继续纵深。。。这里是多包了一层了:
public void initApplication(Application application) { this.application = application; }
这个方法是在一个叫做IocContainer的类里面的,用于全局存放这个application的子类对象。毕竟,这个类有大用处。后面再慢慢说。到此为止,就已经了解了框架的第一步初始化代码,竟然仅仅只是保存一个application子类的对象而已。然后,我们再继续讨论,init方法的第二行代码,因为这行代码有多个方法,咱们一个一个来解剖,先看bind。
Ioc.bind(SimpleValueFix.class);
这行代码,从bind这个单词就可以见名知义,即是将这个类捆绑起来。捆绑起来做啥?就是用来做单例。。。这样以后,你再想引用这个单例类(哪怕这个类本身并无单例模式的实现方式)。知道作用后,咱们再,继续解剖bind的源码:
public static Instance bind(Class<?> clazz) { return IocContainer.getShare().bind(clazz); }
下一层bind类的具体实现。
/** * 对象配置 * @param clazz 类名.class */ public Instance bind(Class clazz) { Instance instance = new Instance(clazz); instance.setAsAlians(new AsAlians() { public void as(Instance ins, String name, Class toClazz) { if (name != null) { if (instanceByName.containsKey(name)) { instanceByName.remove(name); } instanceByName.put(name, ins); } if (toClazz != null) { if (instanceByClazz.containsKey(toClazz)) { instanceByClazz.remove(toClazz); } instanceByClazz.put(toClazz, ins); } } }); return instance; }
这里通过给Instance构造函数传一个Class对象,来构造一个Instance实例。说到这里,又得再先深入Instance类的实现,不知道看客你的递归思维如何,这一层套一层的,或许可以先讲功能,再解剖这个类,但我还是比较喜欢一层一层纵深解剖,这样才不会出现有些代码看后出现,一知半解的情况。但是Instance类过于庞大,咱们这里先讲用到的,看到的,那就先从Instance的构造函数来说。
//对应的类 public Class clazz; //绑定到对象(这是官方注释,但我觉得应该说成,用对象来绑定,更好理解...因为这里的toClaszz是作为key出现的,下面的name字段同理,因为name也是作为key出现,我读的还不够深,或许可能还有别的意思,后面边看边优化) public Class toClazz; //名 public String name; //初始化一个Instance实例 public Instance(Class clazz) { this.clazz = clazz; } //AsAlians类是一个接口 public interface AsAlians { public void as(Instance me, String name, Class toClazz); }
没错…就是这么简单。来,咱们继续看下一行代码,这行代码,简化着来看就是 instance.setAsAlians(new AsAlians());但这里的AsAlians类是一个接口,这里采用匿名类的写法,并通过重写AsAlians类里的方法,来实例化一个AsAlians对象。上面看到的匿名AsAlians的实现代码的触发,是在另一个地方,这里看不看都无所谓,只要知道Instance类里有一个Aslians变量,而且这个变量的as(Instance ins, String name, Class toClazz)方法的具体实现,是在这里就行。
那么,即然这个Bind方法的最复杂的部分,在这里不需要看,那我们就“先”continue,看下一行代码:return instance,噢,没了。。。这个bind方法到此就没了。
Ioc.bind(SimpleValueFix.class)
那我们来总结一下,这行代码做了啥事:
1、将SimpleValueFix类,传给Instance类的构造方法,来实例化一个带有SimpleValueFix类变量(这个变量是Class类,所以,可以赋值所有xxx.class类)的Instance类。
2、给Instance类的实例,赋值一个有具体实现的AsAlians接口类对象。
3、返回一个初始化过的Instance类对象。
4、总结,其实,就是绑定了一个具体的类。
好了,到此,我们就已经知道了bind方法做了什么事。接下来,继续看下一行代码,to方法的实现:
Ioc.initApplication(app); Ioc.bind(SimpleValueFix.class) .to(ValueFix.class) /** * to方法的具体实现 * 需要注入的类型 * @param clazz xxx.class * @return 返回一个Instance对象 */ public Instance to(Class clazz) { this.toClazz = clazz; if (this.asAlians != null) { this.asAlians.as(this, null, clazz); } return this; } //这里我把name方法也放出来,因为as的具体实现里,有一个判断涉及到这个方法。 public Instance name(String name) { this.name = name; if (this.asAlians != null) { this.asAlians.as(this, name, null); } return this; }
这个to方法,是用来将一个类,注入到bind方法中已绑定的类里面。现在,就得review一下前面as方法的具体实现代码了!
instance.setAsAlians(new AsAlians() { public void as(Instance ins, String name, Class toClazz) { if (name != null) { if (instanceByName.containsKey(name)) { instanceByName.remove(name); } instanceByName.put(name, ins); } if (toClazz != null) { if (instanceByClazz.containsKey(toClazz)) { instanceByClazz.remove(toClazz); } instanceByClazz.put(toClazz, ins); } } });
如果name不为空。因为从name方法里面,可以看到as方法,只传name,是因为这里是通过以name为key,来绑定对应的instance对象。而to方法里面,可以看到as方法,只传Class,是因为这里通过以Class为key,来绑定对应的instance对象。到此,对于bind和to方法,我们已经够明白了。
最后,我们来讨论一下最后一个方法,scope方法的实现:
Ioc.initApplication(app); Ioc.bind(SimpleValueFix.class) .to(ValueFix.class).scope(InstanceScope.SCOPE_SINGLETON);
下面是scope方法的具体实现:
/** * 作用域 * @param scope * @return */ public Instance scope(InstanceScope scope) { this.scope = scope; return this; }
从注释可以看出来,这个方法是用来设置作用域的。那么,问题就来了:
1、是谁的作用域?
2、一共有哪些作用域?
为了解答这两个问题,我们有必要继续看一下Instance类的其它代码段:
//保存的对象 public Object obj; //对象的作用域 public InstanceScope scope; // 对象作用域的类别 public enum InstanceScope { // 应用中单例 SCOPE_SINGLETON, // 每次创建一个 SCOPE_PROTOTYPE; } //获取某个对象 public Object get(Context context) { //获取单例 if (scope == InstanceScope.SCOPE_SINGLETON) { if (obj == null) { obj=bulidObj(context); injectChild(obj); } //这里需要先保证自己可以在容器中拿到然后才能注入 return obj; //获取context类型的对象 }else if (scope == InstanceScope.SCOPE_PROTOTYPE) { Object obj=bulidObj(context); //这里需要先保证自己可以在容器中拿到然后才能注入 injectChild(obj); return obj; } return null; }
看完上面的代码后,答案恐怕大家都知道了:
一、 成员变量obj的作用域。
二、作用域有两种:
1、单例,即整个应用只保存一个对象,只要对象存在,就不再创建。(具体可以参考,设计模式之单例模式,但这里,是通过对某一个类的对象的保存来实现单例。即先判断类,如果该类有已存在的对象,就返回该对象。否则,才创建新的)
2、每次都创建。。这个不需要解释了,与new没啥区别了。
这里,我们就只讲解单例模式,即scope == InstanceScope.SCOPE_SINGLETON的情况。由代码,可知,框架首先会先判断当前的obj对象是为空,如果为空就执行一个叫做bulidObj的方法来创建一个obj对象。如果已存在,就直接返回这个obj对象来实现单例效果。那么,问题来了,这个buildObj究竟是创建哪个类的对象呢?为了答案,我们就得继续来解剖一下bulidObj(context)方法,看他是如何创建一个对象的。
/** * 构建对象<br/> * 如果传入的context 不为空会尝试用context构建对象 否者会调用默认构造函数 * @param context * @return 返回一个clazz类对象 */ public Object bulidObj(Context context) { Object obj = null; Constructor construstor = null; if(context!=null){ Constructor[] constructors = clazz.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { Constructor ctr = constructors[i]; Type[] types = ctr.getParameterTypes(); if (types != null && types.length == 1 && types[0].equals(Context.class)) { construstor = ctr; } } } try { if (construstor != null) { obj= construstor.newInstance(context); } else { obj= clazz.newInstance(); } } catch (IllegalArgumentException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } if(obj!=null&&perpare!=null){ perpare.perpare(obj); } return obj; }
这段代码,如果你是大牛,那可以一口气看完,如果跟我一样是小渣渣的话,咱们不妨把这个方法,拆分成两部分,化繁为简,由易入难。先看第一部分:
//context是上面完整代码里,方法的参数。 Object obj = null; Constructor construstor = null; if(context!=null){ /** * getDeclaredConstructors会返回类的多个构造函数 * 并且,他们是无序的存在于这个返回的数组中。 * 如果没有自定义的构造函数,则返回默认构造函数。 * 如果该类是一个接口、数组、void、基本数据类型,则数组的长度为0 */ Constructor[] constructors = clazz.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { Constructor ctr = constructors[i]; Type[] types = ctr.getParameterTypes(); if (types != null && types.length == 1 && types[0].equals(Context.class)) { construstor = ctr; } } }
首先,是定义两个临时变量,一个Object ,一个Constructor。然后,再判断这个context是否为空,如果不非为空,就通过claszz类的getDeclaredConstructors()方法,来获取claszz类的所有构造方法。然后,再判断某个类里面的所有构造方法里面有没有一个构造方法,只需要传一个context传数,就能创建实例的,如果有,就将这个构造方法赋值给Constructor类的对象。如果没有,那么Contructor类的对象则为null。好了,上部分代码就是这么简单,下面我们继续看第二部分。
try { if (construstor != null) { obj= construstor.newInstance(context); } else { obj= clazz.newInstance(); } } catch (IllegalArgumentException e) { } catch (InstantiationException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } //下面这个判断永远不成立,可以删除,对项目并不影响,此处不做分析。 if(obj!=null&&perpare!=null){ perpare.perpare(obj); } return obj;
如果construstor不为空,即存在一个只需一个context参数就能创建实例的构造方法时,就用这个构造方法来创建实例。如果没有,就通过调用Class的newInstance();来创建实例。有人就想问了,那创建的是哪个类的实例呢?为了回答这个问题,我们就得看看这个claszz对象是由谁给赋的值了。细心和记忆力好的朋友,可能就已经发现了,在本文的前段,有这样的代码,这里再贴出来,免得大家还得往上拉去看看。
/** * 对象配置 * @param clazz 类名.class */ public Instance bind(Class clazz) { Instance instance = new Instance(clazz); ....//此处省略该方法的其它代码,具体可以往上拉去看或查看框架源码 }
这里的Instance的clazz参数是的bind方法的clazz参数,直接传递赋值的。所以,你bind传了一个什么类,那么这里clazz.newInstance()就会创建出一个什么类对象来。
到此为止,我们就已经完整的分析了Ioc容器的初始化代码
Ioc.bind(SimpleValueFix.class).to(ValueFix.class).scope(InstanceScope.SCOPE_SINGLETON);
但是,我们平时使用Ioc的时候,都不是Ioc.bind.to.scope这样使用,而是直接通过下面的代码,来创建一个类对象的。
类名 变量名 = Ioc.get(类名.class);
那么,我们就有必要,继续来解剖Ioc的get方法,来看看这个get方法是做啥的,又与前面的Ioc初始化有毛关系。但限于篇幅,剩下的我们就在“Dhroid框架六大组件之Ioc容器【下】”来详细解剖和分析,坚持下去,Ioc容器剩下的内容并不多了。
相关文章推荐
- 插件管理框架 for Delphi(一)
- 使用CSS框架布局的缺点和优点小结
- 一起动手编写Android图片加载框架
- 基于.NET平台常用的框架和开源程序整理
- 列举PHP的Yii 2框架的开发优势
- Windows窗体的.Net框架绘图技术实现方法
- 浅谈JavaScript 框架分类
- 轻量级javascript 框架Backbone使用指南
- javascript实现框架高度随内容改变的方法
- JS刷新框架外页面七种实现代码
- 超赞的动手创建JavaScript框架的详细教程
- 深入探讨前端框架react
- jQuery的框架介绍
- 简单介绍不用库(框架)自己写ajax
- 利用ASP.NET MVC+EasyUI+SqlServer搭建企业开发框架
- asp.net4.0框架下验证机制失效的原因及处理办法
- 插件管理框架 for Delphi(二)
- 零基础学习AJAX之AJAX框架
- Ajax 框架学习笔记
- Flex中最好的MVC框架Mate框架