您的位置:首页 > 其它

Dhroid框架六大组件之Ioc容器【上】

2016-05-17 20:53 435 查看
本文,是假设你有使用或了解过dhroid框架的基础上进行讲解的,如果对该框架并不了解,但想学习的,可以先到这里去学习一下:http://www.oschina.net/p/dhroid。 这里有提供下载地址和简单教程。努力深入学习别人的框架,看看别人是如何从0到1做出一个框架的,当你有能力优化,乃至创建一个新的、更优的框架的时候,你就是下一个大神了,距离升职加薪,迎取白富美,又跨进了一大步!就算迎取不到白富美,至少加薪应该不成问题,有了钱,大宝健杠杠的,大宝健上,要啥白富美没有,是吧!!!好了,不扯蛋了。让我们开始进入主题,走向通往大神之路吧。

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容器剩下的内容并不多了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息