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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值