Spring解析之IoC:bean的加载(二)

前言
上一篇bean加载的文章分析了bean加载核心入口AbstractBeanFactory#doGetBean(String, Class, Object[], boolean)的上半部分,该部分主要逻辑在显式调用getBean时会被执行,更具体的来说是获得单例对象和通过自定义FactoryBean创建对象时会执行上半部分;而下半部分会在初始化和获得prototype多例对象时被执行,本文就是对这下半部分做深入分析

为了分析方便,我们再截取一次下半部分内容的代码,代码清单1

        //    (1)
    if (isPrototypeCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }

    //    (2)
    BeanFactory parentBeanFactory = getParentBeanFactory();
    if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
        // Not found -> check parent.
        String nameToLookup = originalBeanName(name);
        if (args != null) {
            // Delegation to parent with explicit args.
            return (T) parentBeanFactory.getBean(nameToLookup, args);
        }
        else {
            // No args -> delegate to standard getBean method.
            return parentBeanFactory.getBean(nameToLookup, requiredType);
        }
    }
        //    (3)
    if (!typeCheckOnly) {
        markBeanAsCreated(beanName);
    }

    try {
                //    (4)
        final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
        checkMergedBeanDefinition(mbd, beanName, args);

        //    (5)
        String[] dependsOn = mbd.getDependsOn();
        if (dependsOn != null) {
            for (String dependsOnBean : dependsOn) {
                getBean(dependsOnBean);
                registerDependentBean(dependsOnBean, beanName);
            }
        }
                //    (6)
        // Create bean instance.
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                public Object getObject() throws BeansException {
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    catch (BeansException ex) {
                        // Explicitly remove instance from singleton cache: It might have been put there
                        // eagerly by the creation process, to allow for circular reference resolution.
                        // Also remove any beans that received a temporary reference to the bean.
                        destroySingleton(beanName);
                        throw ex;
                    }
                }
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
                //    (7)
        else if (mbd.isPrototype()) {
            // It's a prototype -> create a new instance.
            Object prototypeInstance = null;
            try {
                beforePrototypeCreation(beanName);
                prototypeInstance = createBean(beanName, mbd, args);
            }
            finally {
                afterPrototypeCreation(beanName);
            }
            bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        }
                //    (8)
        else {
            String scopeName = mbd.getScope();
            final Scope scope = this.scopes.get(scopeName);
            if (scope == null) {
                throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
            }
            try {
                Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        beforePrototypeCreation(beanName);
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        finally {
                            afterPrototypeCreation(beanName);
                        }
                    }
                });
                bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
            }
            catch (IllegalStateException ex) {
                throw new BeanCreationException(beanName,
                        "Scope '" + scopeName + "' is not active for the current thread; " +
                        "consider defining a scoped proxy for this bean if you intend to refer to it from a singleton",
                        ex);
            }
        }
    }
    catch (BeansException ex) {
        cleanupAfterBeanCreationFailure(beanName);
        throw ex;
    }

我们都知道对于scope = "prototype"的对象来说,Spring是不会在真正使用该对象前创建它的,这一特性意味着Spring不能解决prototype类型变量的循环依赖问题,道理很简单,当我们真正使用prototype对象时肯定要求内部的依赖关系都建立完毕,而循环依赖的存在使这种对象间关系无法建立,必定就会抛出BeanCurrentlyInCreationException
上一篇文中说过,Spring使用“三级缓存”的形式能部分解决循环依赖问题,我们在这里就可以总结下“部分”指哪些情况:Spring能够解决singleton下使用setter方式形成的循环依赖问题,不能够解决singleton下使用构造器形成的循环依赖,以及prototype下的循环依赖。第一种能解决的原因是Spring解析之IoC:bean的加载(一)中分析过的缓存earlySingletonObjects,当我们设置允许早期对象引用暴露allowEarlyReference后,Spring会将通过构造器创建出来的,尚未调用setter建立依赖关系的单例对象放入earlySingletonObjects中,当我们需要一组存在循环依赖的对象时直接从里面取出每个对象手动建立依赖关系即可。但是如果是构造器形成的循环依赖,连创建早期对象都不可能,自然就没法解决循环依赖问题了
重温了Spring解决循环依赖的手段后看标注1,如果获取的是prototype类型的对象且该对象正在创建中,必然就发生了循环依赖,直接抛出异常。标注2是存在父子容器时加载bean的代码逻辑,如果大家用过SpringMVC相信对父子容器都不陌生,大多数情况下Web层的SpringMVC都作为Service层和Dao层Spring容器的子容器存在,子容器可以访问父容器中的bean,反过来则不行,当子容器加载bean时首先判断自身容器中是否存在同名的bean,存在直接获得,不存在就去父容器中查找该名称的bean
标注2的逻辑就是这样,判断如果存在父容器,且当前容器中不存在beanName对应的bean就调用父容器parentBeanFactorygetBean(String, Object...),又来了一个大循环。标注3中typeCheckOnly表示获得bean的目的是否是为了类型检查,而不是真正使用这个bean,绝大部分情况我们当时是要用bean啦,这时就要调用markBeanAsCreated(String)将创建bean的行为做记录,记录实际上就是将beanName打上已经创建的标识放入Map<String, Boolean> alreadyCreated
标注4在Spring解析之IoC:bean的加载(一)中已经说过,如果<bean>存在父子关系(注意不是容器的父子关系),getMergedLocalBeanDefinition(String)会将父子关系的<bean>信息融合在RootBeanDefinition

图1. 检查融合后的BeanDefinition

checkMergedBeanDefinition(RootBeanDefinition, String, Object[])排除了两种情况下bean的创建:1. <bean>存在abstract属性;2. scope = singleton且参数args有值的情况。第一种情况很好理解,都抽象了还创建毛线啊,而要想通第二种情况我们需要追根溯源看看args来自哪里。显式调用getBean获得对象有一个重载方法Object getBean(String, Object...),这里的args就是第二个参数,该重载方法提供的目的是解决一个场景:初始化只存在有参构造的类,且参数要在获取时动态指定。创建只存在有参构造器的类很容易,直接<constrcut-arg>指定就好,但是要每次创建的参数值不同再用该方法很明显就挂了啊,还是Object getBean(String, Object...)好使。这也很好的解释了为什么args不能和singleton共存,因为args说明可变性,singleton说明唯一性,相互冲突
标注5涉及到一个之前没有讲过的<bean>属性depends-on,该属性的作用是让depends-on内的对象先于<bean>所代表的对象创建,但这两组对象并不要求有真正的依赖关系,我们举个例子,创建三个类ManWomanFamilyFamily配置depends-on前两个类

图2. Man

图3. Woman

图4. Family

注意FamilyManWoman只是组合关系并没有形成依赖,在XML进行配置如下
图5. Man、Woman和Family相关配置

如果depends-on多个对象,多个对象之间可以用,隔开,运行结果如下
图6. depends-on例子运行结果

很明显Spring先初始化了depends-on的对象,至于多个depends-on对象创建之间的顺序和XML中对应<bean>书写顺序有关和depends-on中的顺序无关,其实就是Spring自上而下解析标签的顺序。让我们再回到代码清单1,标注6、7、8三处很明显根据scope的不同将处理逻辑分成了三块,为了分析清楚我们将每一块单独拎出来
图7. 处理singleton对象逻辑

scope = singleton对象的处理也分为三部分,1、2两部分是互有关联的,Object getSingleton(String, ObjectFactory)第二个参数是接口ObjectFactory的匿名实现,实现了Object getObject()方法,具体的实现又调用了Object createBean(String, RootBeanDefinition, Object[]),而该方法又是一个模板,真正的具体实现在AbstractAutowireCapableBeanFactory中,我们先走进标注1看看做了什么
图8. DefaultSingletonBeanRegistry的getSingleton(String, ObjectFactory<?>)

首先从缓存singletonObejcts中获取该单例对象,不存在进入创建流程,beforeSingletonCreation(String)做创建对象前的处理工作,之后调用匿名实现的getObject()进而调用上面说的模板方法createBean创建对象,afterSingletonCreate(String)做一些后处理操作,最后addSingleton(String, Object)将创建的单例对象放入缓存
图9. DefaultSingletonBeanRegistry的beforeSingletonCreation(String)

isCreationCheckExclusions保存在创建时不需要做校验的bean名称,singletonCurrentlyInCreation大家应该很熟悉了,保存正在创建过程中的对象,整体逻辑就是,如果对象在创建时需要做校验(说明还没真正创建),但在正在创建对象的容器中又有它,那就说明有问题,抛出BeanCurrentlyInCreationException。同时这一步也让大家知道了用于检测循环依赖的singletonCurrentlyInCreation是什么时候被塞入内容的
分析了这么多还在外围转悠,下面的singletonFactory.getObject()是创建bean的核心代码了吧?是也不是,是是因为核心创建流程确实在该方法中,不是是因为小小的方法里面涉及的东东那多的啊,Spring的东西果然浩瀚如海啊。正因为这个问题的存在我想还是将核心逻辑再开一篇文章单独分析吧,要不然这篇文章得写多少啊,读者伤心写者流泪啊。我们现在只需知道singletonFactory.getObject()主要得到的对象就两种:1.和<bean>对应的真实bean;2.创建bean的自定义FactoryBean实例,本篇文章先将外围逻辑都清理干净
afterSingletonCreation(String)闭着眼睛想都知道是创建单例之后的处理逻辑,和beforeSingletonCreation(String)唯一不同的在于,后者是将正在创建的对象放入singletonsCurrentlyInCreation,而前者是创建完对象后从singletonsCurrentlyInCreation移除。addSingleton(String, Object)逻辑也很简单
图10. DefaultSingletonBeanRegistry的addSingleton(String, Object)

Spring解析之IoC:bean的加载(一)中提到部分解决循环依赖的“三级缓存”,也说到过数据只能存在其中一个缓存中,这里就是当对象创建完成后将对象放入“一级”缓存中并删除其余缓存中的该对象流程,并在registeredSingletons已注册对象HashSet中保存对应的beanName
回到图7,标注1、2都分析过了,标注3更好说了,同样在Spring解析之IoC:bean的加载(一)中已经进行了详细的分析,如果生成对象为bean直接返回,如果是自定义FactoryBean,调用其实现的getObject()创建bean后返回,单例创建对象分析完毕,开始多例对象创建分析
图11. 处理prototype对象逻辑

从宏观上看scope = prototype处理流程和单例时一样,围绕createBean(String, RootBeanDefinition, Object[])进行前后校验处理,最后将可能的FactoryBean转成特定的bean返回
图12. AbstractBeanFactory的beforePrototypeCreation(String)

prototypesCurrentlyInCreation是一个ThreadLocal<Object>变量,其中保存了当前线程正在创建的所有多例对象,只存在一个多例对象时ThreadLocal内存的就是字符串beanName,当有多个时内部存储的就是HashSet集合,这里的存储又和本文最开始用prototypesCurrentlyInCreation做多例类型循环依赖的判断对应上了。createBean(String, RootBeanDefinition, Object[])和上面一样暂时跳过,afterPrototypeCreation(String)思路和单例的后处理相似,将创建好的多例对象从prototypesCurrentlyInCreation中移除,最后一步getObjectForBeanInstance(Object, String, String, RootBeanDefinition)和之前分析的一模一样,不再赘述。最后一组是剩下所有scope对象的处理逻辑,除了我们最常用的singletonprototype外,针对Web项目Spring又提供了requestsessionglobal session等其他类型(不同Spring版本scope也不一样,到时大家看到多几个少几个不用诧异),此外我们还可以实现Scope接口创建自定义的scope,这里给一篇文章上面有Spring3.0相关scope类型的用法讲解,大家可以拿来耍耍Bean scopes
图13. 处理其他scope对象逻辑

其实其他scope类型创建bean的逻辑从上图看和scope = “prototype”相似,先从RootBeanDefinition中得到配置的scope,然后从Map<String, Scope> scopes中得到该scope对应的处理类,根据处理类中实现的Object get(String, ObjectFactory<?>),如果实现的逻辑中调用了ObjectFactoryObject getObject(),那就又回到了多例的处理逻辑,剩下的大家看前面的分析即可

后记
本文将getBean中最后零碎的逻辑清理干净就是为了将核心创建bean作为一个整体分析,即便如此由于Spring涉及的内容太多,createBean依然比较庞大和杂乱,不管怎么说我们已经吹响了最后的冲锋号,年前必定攻下获得bean这座山头,加油!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容