spring 源码深度解析_源码深度解析spring的循环引用(三)——终章

前言

大家好,我是子路,一个靠Java吃饭的男人

前两篇文章:

从源码的角度来解读spring的循环引用(一)——生命周期

源码深度解析spring的循环引用(二)——逐条解读代码

已经介绍完了循环依赖在Bean生命周期中的体现,以及对每一条代码做出了诠释。这篇文章将接着上面两篇文章,完成Spring当中的循环应用的讲解。

正文

我先给出这个方法的源码全貌;重点我用红色标记了,并且会在进行代码解析;黄色线下面的读者可以不用管,和本文内容没多大关系;

读者可以好好看看下图:方便你阅读下面的代码解析

e804cee4da96f9b5ac2c57bed96c1b20.png

//doCreateBean -1

instanceWrapper = createBeanInstance(beanName, mbd, args);

createBeanInstance 顾名思义就是创建一个实例,注意这里仅仅是创建一个实例对象,还不能称为bean;因为我文章一开头就解释了什么是bean,什么是对象;好吧再啰嗦一下吧,文章比较长,不方便翻阅;

1、spring bean——受spring容器管理的对象,可能经过了完整的spring bean生命周期(为什么是可能?难道还有bean是没有经过bean生命周期的?答案是有的,具体我们后面文章分析),最终存在spring容器当中;一个bean一定是个对象2、对象——任何符合java语法规则实例化出来的对象,但是一个对象并不一定是spring bean;

同样用dubug来说明一下:

注意这是张gif,如果你看着不动请参考我上面说的方法

9c22e0fd220aab2401d0b45cb7ad4713.gif

运行完createBeanInstance之后控制打印了X构造方法的内容,说明X对象已经被创建了,但是这个时候的x不是bean,因为bean的生命周期才刚刚开始;这就好比你跑到天上人间,问了各种你想问的问题之后交了1000块钱,但是这个时候你仅仅是个消费者,还不是渣男,因为一条龙的服务是从交钱开始,接下来的各种服务完成你才是一个名副其实的渣男,不知道这么解释有没有偏差;为了把前面知识串起来,照例画一下当前代码的语境吧:

385dc4338e785ab28849aafa7b23adcc.png

这个createBeanInstance方法是如何把对象创建出来的呢?对应文章开头说的bean的生命周期一共17步,其中的第8步(推断构造方法)和第9步(利用构造方法反射来实例化对象);具体如何推断构造方法我会在后面的博客分析;这里截个图看看代码就行,不做分析;

推断构造方法的代码运行结果分析——注意这张图比较长,读者可以多看几遍;因为推断构造方法笔者以为是属于spring源码中特别重要和特别难的一块知识;后面会有单独博客来分析,所以读者可以先多看看这张图;

注意这是张gif,如果你看着不动请参考我上面说的方法

892491a7d587ded3c66c22db00c38a7a.gif

至此x对象已经实例化出来,代码往下执行到合并beanDefinition,看图吧:

29991dac21dfd14a45ae1acae136b5d2.png

但是其实合并beanDefinition和本文讨论的循环依赖无关,故而先跳过;

//doCreateBean-2 开始解析

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&            isSingletonCurrentlyInCreation(beanName));

这段代码其实比较简单,就是给earlySingletonExposure这个布尔类型的变量赋值;这个变量的意义是——是否支持(开启了)循环依赖;如果返回true则spring会做一些特殊的操作来完成循环依赖;如果返回false,则不会有特殊操作;

回到天上人间那个问题,好比你去一条龙的时候;人家会分析你是否是雏,如果你是雏则随便给你安排一个技师;当然如果你是笔者这样的资深玩家,可能会安排新垣结衣也说不定;

那么这个布尔变量的赋值逻辑是怎样的呢?上面代码可知三个条件做&&运算,同时成立才会返回true;

1、mbd.isSingleton();判断当前实例化的bean是否为单例;再一次说明原型是不支持循环依赖的;因为如果是原型这里就会返回false,由于是&&运算,整个结果都为false;相当于人家判断你是雏;那么新垣结衣什么的就别想了;在本文环境里X是默认单例的,故而整个条件是true;


2、this.allowCircularReferences;整个全局变量spring 默认为true;当然spring提供了api供程序员修改,这个在本文开头笔者解释过(笔者是通过修改spring源码来改变这个值为false),在没有修改的情况下这里也返回true;


3、isSingletonCurrentlyInCreation(beanName);判断当前正在创建的bean是否在正在创建bean的集合当中;还记得前文笔者已经解释过singletonsCurrentlyInCreation这个集合现在里面存在且只有一个x;故而也会返回true。

其实这三种情况需要关心的只有第二种;因为第一种是否单例一般都是成立的,因为如果是原型的循环依赖前面代码已经报错了;压根不会执行到这里;第三种情况也一般是成立,因为这个集合是spring操作的,没有提供api给程序员去操作;而正常流程下代码执行到这里,当前正在创建的bean是一定在那个集合里面的;换句话说这三个条件1和3基本恒成立;唯有第二种情况可能会不成立,因为程序员可以通过api来修改第二个条件的结果;

总结:spring的循环依赖,不支持原型,不支持构造方法注入的bean;默认情况下单例bean是支持循环依赖的,但是也支持关闭,关闭的原理就是设置allowCircularReferences=false;spring提供了api来设置这个值;

至此我们知道boolean earlySingletonExposure=true,那么代码接着往下执行;判断这个变量;

15d8127e2f9d3bf6d4805c0b81748d07.png

if成立,进入分支;

//doCreateBean-3 开始分析

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));这段代码又用了lamda表达式;笔者为了初学者看懂,还是改成传统代码ObjectFactory> singletonFactory = new ObjectFactory>(){    public T getObject(){        //至于这个getEarlyBeanReference方法的代码,后面再来说        // 现在可以理解为就是返回 bean        getEarlyBeanReference(beanName, mbd, bean);        //getEarlyBeanReference 的代码稍微复杂一点,可以简单理解为下面这样        getEarlyBeanReference(beanName, mbd, bean){            return bean;        }    }}也就是singletonFactory.getObject();其实就是返回当前正在实例化的bean改完之后的代码可以理解成这样:addSingletonFactory(beanName,singletonFactory);

addSingletonFactory(beanName,singletonFactory):顾名思义添加一个单例工厂;其实这里要非常注意,因为大部分资料里面在说到spring循环依赖的时候都说是提前暴露一个半成品bean;笔者觉得这个不严格;甚至算错误了,所谓的提前暴露就是这里的add,但是我们看到源码并不是add一个bean的,而是add一个工厂对象——singletonFactory;

两种说法有什么区别呢?区别可大了,简直天壤之别;我们慢慢分析;这里bean和工厂有什么区别呢?在当前的语境下面bean就是x对象经历完spring生命周期之后;所谓的半成品bean,可能还没有经历完整的生命周期;

而工厂对象呢?如果你去ObjectFactory的源码或者直接顾名思义,它是一个能够产生对象的工厂,或者叫能够产生bean的工厂;换句话说bean是一个产品,而工厂是产生这些产品的公司;如果还不能理解换成天上人间可能好理解——冰火和全套的区别,冰火是全套里面的一个项目,除了冰火还有其它项目;

那么spring在这里add的是singletonFactory这个工厂对象(这个工厂可以产生半成品对象),而不是一个半成品对象;相当于这里add的是全套,而不是冰火;将来拿出来的时候是得到工厂,继而通过工厂得到半成品bean;将来拿出来的是全套,你可以在全套里面肆意选择一个项目;不知道我有没有解释清楚这个问题;

当然说了这么多可能你还是没明白为什么需要在这里add这个工厂对象呢?还有add到哪里去呢?

我们首先分析bean工厂对象到底add到哪里去了,查看源码:

a6953b5137b934df423812031fac3039.png

读者可以好好看看上图,笔者在spring源码当中把注释写上了(注释的信息很重要,认真看看),整个方法其实就是对三个map操作,至于这三个map的意义,参考下图:

e6ba52861a6ce00b791d001f7921acbf.png

通过代码可以得知singletonFactory主要被add到二级缓存中;至于为什么要add到这个map?主要了循环依赖,提前暴露这个工厂;当然如果你不理解为什么要提前暴露,没关系往下看,看完文章一定会知道的;

保持好习惯照例画个图,让读者知道现在的情况吧:

56924470b0d9dfb4e8db9374c3c68922.png

当然这里还是用一幅图来描述一下这个三个map的各种情况吧:

09b84ba73639a2cc716ca7f24b89b1f9.png

一级缓存:可能存在很多bean,比如spring各种内置bean,比如你项目里面其它的已经创建好的bean,但是在X的创建过程中,一级缓存中绝对是没有xbean的,也没用y;因为spring创建bean默认的顺序是根据字母顺序的;

二级缓存:里面现在仅仅存在一个工厂对象,对应的key为x的beanName,并且这个bean工厂对象的getObect方法能返回现在的这个时候的x(半成品的xbean)
put完成之后,代码接着往下执行;

三级缓存:姑且认为里面什么都没有吧!

//doCreateBean-4 开始解析

populateBean(beanName, mbd, instanceWrapper);

populateBean这个方法可谓大名鼎鼎,主要就是完成属性注入,也就是大家常常说的自动注入;假设本文环境中的代码运行完这行代码那么则会注入y,而y又引用了x,所以注入进来的y对象,也完成了x的注入;什么意思呢?首先看一下没有执行populateBean之前的情况。

cddf66f4878274edce954b41772679ed.png

没有执行populateBean之前只实例化了X,Y并没实例化,那么Y也不能注入了;接下来看看执行完这行代码之后的情况:

ae0a75c39f2786fc301fcb2bc2bc0b12.png

populateBean里面的代码以后我更新文章来说明,本文先来猜测一下这个方法里面究竟干了什么事;
x 填充 y (简称 xpy)首先肯定需要获取y,调用getBean(y),getBean的本质上文已经分析过或进入到第一次调用getSingleton,读者可以回顾一下上文我对doGetBean方法名字的解释里说了这个方法是创建bean和获取共用的;

第一次getSingleton会从单例池获取一下y,如果y没有存在单例池则开始创建y;

创建y的流程和创建x一模一样,都会走bean的生命周期;比如把y添加到正在创建的bean的集合当中,推断构造方法,实例化y,提前暴露工厂对象(二级缓存里面现在有两个工厂了,分别是x和y)等等。。。。重复x的步骤;

直到y的生命周期走到填充x的时候ypx,第一次调用getSingletion获取x?这里问个问题,能否获取到x呢?

在回答这个问题之前我们先把该画的图贴出来,首先那个正在被创建bean的集合已经不再是只有一个x了;(读者可以对比一下上文的图)

7af4ac1e885cbe2fd114c980eab87e5d.png

然后我们再把xpy到ypx的流程图贴出来,请读者仔细看看

5c4aa7154a7975a25ff8bc1734de376c.png

是否能够获取到x呢?首先我们想如果获取失败则又要创建x—>实例化x—填充属性----获取y--------。。。。。。。就无限循环了;所以结果是完成了循环依赖,那么这里肯定能够获取到x;那么获取到x后流程是怎样呢?

af907f1abb4f08196af8bc88fb09ccec.png

那么为什么能够获取到x呢?讲道理联系上文第一次调用getSingleton是无法获取到x的?因为我们上面说过第一次调用getSingleton是从单例池当中获取一个bean,但是x显然没有完成生命周期(x只走到了填充y,还有很多生命周期没走完),所以应该是获取不到的?为了搞清楚这个原因得去查看第一次getSingleton的源码;如果读者有留意的话笔者前面只是凭只管告诉你第一次getSingleton是从单例池当中获取一个bean,并没有去证明,也就是没有去分析第一次getSingleton的源码;而且我在总结第一次getSingleton的时候用了目前这个词;证据如下(图是本文前面的内容,为了翻阅方便我直接贴这里了)

774c8b063c2f73e885e59d6c095a951c.png

显然这是笔者前面故意挖的坑,所以各位读者在阅读别人的文章或者书籍的时候一定要小心验证;包括笔者的文章如果有错误一定记得告诉我;

下面来开始对第一次getSIngleton源码做深入分析;首先把源码以及我写的注释贴出来,分为图片和源代码,建议大家看图片,可读性好。

5498670f4510be8eddc1ac25e22768aa.png

源码:如果你仔细看了上面的图片可以跳过这里的源码展示。

protected Object getSingleton(String beanName, boolean allowEarlyReference) {        //从单例池当(一级缓存)中直接拿,也就是文章里面'目前'的解释        //这也是为什么getBean("xx")能获取一个初始化好bean的根本代码        Object singletonObject = this.singletonObjects.get(beanName);        //如果这个时候是x注入y,创建y,y注入x,获取x的时候那么x不在容器        //第一个singletonObject == null成立        //第二个条件判断是否存在正在创建bean的集合当中,前面我们分析过,成立        //进入if分支        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {            synchronized (this.singletonObjects) {                //先从三级缓存那x?为什么先从三级缓存拿?下文解释                singletonObject = this.earlySingletonObjects.get(beanName);                //讲道理是拿不到的,因为这三个map现在只有二级缓存中存了一个工厂对象                //回顾一下文章上面的流程讲工厂对象那里,把他存到了二级缓存                //所以三级缓存拿到的singletonObject==null  第一个条件成立                //第二个条件allowEarlyReference=true,这个前文有解释                //就是spring循环依赖的开关,默认为true 进入if分支                if (singletonObject == null && allowEarlyReference) {                    //从二级缓存中获取一个 singletonFactory,回顾前文,能获取到                    //由于这里的beanName=x,故而获取出来的工厂对象,能产生一个x半成品bean                    ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);                    //由于获取到了,进入if分支                    if (singletonFactory != null) {                        //调用工厂对象的getObject()方法,产生一个x的半成品bean                        //怎么产生的?下文解释,比较复杂                        singletonObject = singletonFactory.getObject();                        //拿到了半成品的xbean之后,把他放到三级缓存;为什么?下文解释                        this.earlySingletonObjects.put(beanName, singletonObject);                        //然后从二级缓存清除掉x的工厂对象;?为什么,下文解释                        this.singletonFactories.remove(beanName);                    }                }            }        }

针对上面的源码我做一个简单的总结:首先spring从单例池当中获取x,前面说过获取不到,然后判断是否在正在创建bean的集合当中,前面分析过这个集合现在存在x,和y;所以if成立进入分支;进入分支spring直接从三级缓存中获取x,根据前面的分析三级缓存当中现在什么都没有,故而返回nll;进入下一个if分支,从二级缓存中获取一个ObjectFactory工厂对象;根据前面分析,二级缓存中存在x,故而可以获取到;跟着调用singletonFactory.getObject();拿到一个半成品的x bean对象;然后把这个x对象放到三级缓存,同时把二级缓存中x清除(此时二级缓存中只存在一个y了,而三级缓存中多了一个x);

问题1

为什么首先是从三级缓存中取呢?

主要是为了性能,因为三级缓存中存的是一个x对象,如果能取到则不去二级找了;

那有人会问二级有什么用呢?为什么一开始要存工厂呢?为什么一开始不直接存三级缓存?

这里稍微有点复杂,如果直接存到三级缓存,只能存一个对象,假设以前存这个对象的时候这对象的状态为xa,但是我们这里y要注入的x为xc状态,那么则无法满足;但是如果存一个工厂,工厂根据情况产生任意xa或者xb或者xc等等情况;

比如说aop的情况下x注入y,y也注入x;而y中注入的x需要加代理(aop),但是加代理的逻辑在注入属性之后,也就是x的生命周期周到注入属性的时候x还不是一个代理对象,那么这个时候把x存起来,然后注入y,获取、创建y,y注入x,获取x;拿出来的x是一个没有代理的对象;但是如果存的是个工厂就不一样;首先把一个能产生x的工厂存起来,然后注入y,注入y的时候获取、创建y,y注入x,获取x,先从三级缓存获取,为null,然后从二级缓存拿到一个工厂,调用工厂的getObject();spring在getObject方法中判断这个时候x被aop配置了故而需要返回一个代理的x出来注入给y。

当然有的读者会问你不是前面说过getObject会返回一个当前状态的xbean吗?

我说这个的前提是不去计较getObject的具体源码,因为这块东西比较复杂,需要去了解spring的后置处理器功能,这里先不讨论,总之getObject会根据情况返回一个x,但是这个x是什么状态,spring会自己根据情况返回。

问题2

为什么要从二级缓存remove?

因为如果存在比较复杂的循环依赖可以提高性能;比如x,y,z相互循环依赖,那么第一次y注入x的时候从二级缓存通过工厂返回了一个x,放到了三级缓存,而第二次z注入x的时候便不需要再通过工厂去获得x对象了。因为if分支里面首先是访问三级缓存;至于remove则是为了gc吧。

至此循环依赖的内容讲完,有错误欢迎指正,欢迎留言提问;

另外,欢迎关注笔者的B站:子路玩Java,里面有更加详细的视频讲解,后期我也会将视频传至西瓜视频,敬请期待!

我是子路,一个靠Java吃饭的男人。如果觉得还不错,请点个关注~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值