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

来源:互联网 发布:苗木花卉识别软件 编辑:程序博客网 时间:2024/06/05 21:51

本文,是假设你有使用或了解过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容器剩下的内容并不多了。

1 0
原创粉丝点击