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

前言

我是子路,一个把Java当饭吃的人。

笔者之前在华南谷歌搬砖,在系统架构设计、分布式、微服务、高并发、高可用等技术架构具有丰富的实战经验。对市面上主流的开源框架源码——spring、nacos,springboot、JDK并发工具等等都有深入的研究。

Spring是Java语言里面一个非常重要的框架,可以说任何一个学Java的人都必须要接触到Spring。

这里笔者先给大家好好从源码的角度来讲讲Spring。

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

上文讲到了Spring Bean的一个生命周期,以及用了一些源码和步骤来佐证。

这篇文章,笔者就来对上文出现的代码逐行来解释。

正文

1、deGetBean-1

Object sharedInstance = getSingleton(beanName);

首先这行代码上有一句spring作者写的注释

Eagerly check singleton cache for manually registered singletons.

大概的意思就是检查一下单例池当中有没有手动注册的单例对象,说白了spring在创建一个bean之前先检查一下beanName是否被手动注册过到单例池当中;别小看这句spring作者写的javadoc背后的意义,其实这里有两重意思;要搞清楚这两重意思首先知道当代码执行到这里的时候其实是spring在初始化的时候执行过来的;既然spring在初始化的时候他肯定知道这个类X.java肯定没有在容器当中,为什么还需要去验证一下呢?

好比说你第一次去天上人间,你几乎都能确定这是你一次去你不可能跑到那里问一下前台你有没有办会员吧?但是spring确这样做了,他问了,他问问自己有没有办会员;为什么呢?回到你自己,如果你去问自己有没有办会员无非就是怕别人拿着你的身份证去办了一个会员,或者各种原因阴差阳错别人吧身份证名字写错了,导致你成了天上人间的会员;其实spring也是这个意思,因为一个bean被put到单例池的渠道有很多;除了spring容器初始化—扫描类----实例化-----put到容器这条线之外还有很多方法可以把一个对象put到单例池;我这里只列举一种,其他的有机会再讨论,看下图注意注释:

这就相当于在你第一次抱着紧张心态去天上人间的时候,发现你朋友以前拿着你的身份证去那里办了一个会员卡一样;

所以上面提到的这句注释的两重意思①第一重意思判断spring当前正准备初始化的bean有没有提前被put到容器;

那么第二重意思是什么呢?既然这里用来做spring初始化的工作,为什么这个方法名叫做doGetBean呢?讲道理应该叫做createBean啊才合理啊;

有读者可能会说这个方法命名可能作者乱写的,请注意spring之所以经久不衰命名规范绝对是一个重要原因,作者是不会这么乱给方法命名的。诚然有的读者会说讨论这个的意义不大,其实博主觉得讨论这个非常重要;之所这里叫做doGetBean的原因就是因为这个方法就是用来获取bean的,他主要的工作不仅仅服务于spring bean的初始化;这个方法的作用不仅仅是为了spring 在初始化bean的过程中去判断一下这个bean是否被注册了这么简单;笔者认为这个方法最主要的作用是为了从容器中得到一个bean,也就是说当我们在spring代码中调用getBean(“a”)其背后的意义就是调用这个doGetBean,同样用一段代码来证明:

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

可以看到当我调用ac.getBean(“x”)的时候,底层其实就调用doGetBean获取这X对象的;spring之所以这么设计就是因为判断bean是否初始化好和get一个bean都需要从单例池当中获取,所以创建bean和getBean都需要调用这个doGetBean方法;也就是第②重意思,这个方法其实就是程序员getBean的底层实现;

换成天上人间,你第一次跑去前台,人家前台直接说:先生请出示会员卡;你可能会奇怪——我是来全套的,你应该问我要什么服务,不是问会员卡;但是人家前台的职责有两,办会员和问你要什么服务;所以才会说出这句话;doGetBean也是这个意思,于是解释了这个方法名的意义了;

总结一下 Object sharedInstance = getSingleton(beanName);目前看来主要是用于在spring初始化bean的时候判断bean是否在容器当中;以及供程序员直接get某个bean。

注意笔者这里用了 目前这个词;因为getSingleton(beanName);这个方法代码比较多;他里面的逻辑是实现循环依赖最主要的代码,文章下面我会回过头再来讲这个方法的全部意义;

请注意我们当前代码的场景,当前代码是spring容器在初始化的时候,初始化X这个bean的场景;运行到了Object sharedInstance = getSingleton(beanName);
根据上面的分析,这个时候我的X Bean肯定没有被创建,所以这里返回sharedInstance = =null;

跟着解析 //deGetBean-2

//deGetBean-2
if (sharedInstance != null && args == null) {
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);

由于 sharedInstance = =null 故而不会进入这个if分支,那么什么时候不等于null呢?两种情况1、在spring初始化完成后程序员调用getBean(“x”)的时候得到的sharedInstance 就不等于null;2、循环依赖的时候第二次获取对象的时候这里也不等于空;比如X 依赖 Y;Y依赖X;spring做初始化第一次执行到这里的时候X 肯定等于null,然后接着往下执行,当执行到属性注入Y的时候,Y也会执行到这里,那么Y也是null,因为Y也没初始化,Y也会接着往下执行,当Y执行到属性注入的时候获取容器中获取X,也就是第二次执行获取X;这个时候X则不为空;至于具体原因,读者接着往下看;

至于这个if分支里面的代码干了什么事情,本文不讨论,放到后面写factoryBean的时候讨论,现在你可以理解if分支里面就把sharedInstance 原原本本的返回出来就行;即这个if分支没有意义;

上文说了本次不进入if分支,所以这行代码解析完毕;

接下解析 doGetBean -3

else{
        deGetBean-3
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
      }
如果把throw删了可能更加清晰吧,下面是删除后的代码
if (isPrototypeCurrentlyInCreation(beanName)) {}

不进if分支,则进入这个else分支,把throw删了 就一句代码;判断当前初始化的bean----X 是不是正在创建原型bean集合当中当中?

spring源码当中关于这行代码有两行javadoc:

比较简单我就不翻译了,一般情况下这里返回false,也就是不会进入if分支抛异常;为什么说一般情况下呢?首先这里是判断当前的类是不是正在创建的原型集合当中,即里面只会存原型;一般情况下我们的类不是原型,而是单例的,大家都知道spring默认是单例;所以返回false,再就是即使这个bean是原型也很少会在这里就存在正在创建的原型集合当中。

因为不管单例还是原型,bean在创建的过程中会add到这个集合当中,但是创建完成之后就会从这个集合remove掉(关于这个文章后面有证明),原型情况第一次创建的时候会add到这个集合,但是不是在这里,而是在后面的创建过程中add,所以这里肯定不会存在,即使后面过程中add到这个集合了,但是创建完成之后也会remove掉,故而下一次实例化同一个原型bean(原型可以实例化无数次)的时候当代码执行到这里也不可能存在集合当中了;除非循环依赖会在bean还没有在这个集合remove之前再次判断一次,才有可能会存在,故而我前面说了一般情况下这里都返回false;那么单例情况我们已经说了一定返回false,原型情况只有循环依赖才会成立,但是只要是正常人就不会对原型对象做循环依赖的;即使你用原型做了循环依赖这里也出抛异常(因为if成立,进入分支 throw exception)。再一次说明原型不支持循环依赖(当然你非得用原型做循环依赖,其实有办法,以后文章说明,本文忽略);

画了一幅图说明上面的文字,因为这个集合非常重要,但是读者如果这里不理解也没关系,文章下面我还会结合代码分析一次。

重点来了:说明叫做正在创建的原型集合呢? 还有一个与之对应的叫做正在创建的单例集合
唯一的区别就是集合里面存的是单例和原型
故而我们统称正在创建的集合,关于正在创建的集合是什么我下面会解释
但是需要记住的,这个集合是我的一家之言,说白了这是笔者自己翻译的,叫做正在创建的集合,没有官方支持,至少我也没在书上看到过这个名词

下面解析doGetBean-4

else{
        //doGetBean-4
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  return createBean(beanName, mbd, args);
               }
               catch (BeansException ex) {
                  destroySingleton(beanName);
                  throw ex;
               }
            });
   同样把抛异常的代码删了,如下
    //doGetBean-4
        if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
                  return createBean(beanName, mbd, args);
            });

代码有点多;if (mbd.isSingleton()) 比较简单,判断当前bean是否单例;本文环境下是成立的;继而

sharedInstance = getSingleton(beanName, () -> {
                  return createBean(beanName, mbd, args);
            });

这里又调用了一次getSingleton,如果有印象上面也调用了一次getSingleton,这是方法重载,两个getSingleton方法并不是同一个方法,读者自己看参数就行,为了区别我这这里叫做第二次调用getSingleton;上文的叫做第一次调用getSingleton;

由于这里使用lamda表达式,有些读者看起来不是很理解;笔者改一下吧

ObjectFactory<?>  singletonFactory = new ObjectFactory(){
    public Object getObject(){
        //其实这是个抽象类,不能实例化
        //createBean是子类实现的,这里就不关心了
        //你就理解这不是一个抽象类吧
        AbstractBeanFactory abf = new AbstractBeanFactory();
        Object bean = abf.createBean(beanName, mbd, args);
        return bean;
    };
};
//传入 beanName 和singletonFactory 对象
sharedInstance = getSingleton(beanName,singletonFactory);

这样看是不是明白多了呢?

当然第二次getSingleton就会把我们bean创建出来,换言之整个bean如何被初始化的都是在这个方法里面;至此本文当中笔者例举出来的doGetBean方法的核心代码看起来解析完成了;

注意我说的是本文当中例举的doGetBean代码,前面我已经说了我删了很多和循环依赖无关的代码,实际spring源码当中这个方法的代码很多,以后文章介绍吧;

接下来就要研究第二次getSingleton方法的内容了,因为我说了整个bean初始化过程都在里面体现了;

我先把spring源码贴出来,读者可以忽略这里,因为下面会精简代码;之所以贴出源码就是想告诉读者,为了研究循环依赖,本文中的很代码我是做了删减的;

spring源码:-----读者可以忽略
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                catch (IllegalStateException ex) {
                    // Has the singleton object implicitly appeared in the meantime ->
                    // if yes, proceed with it since the exception indicates that state.
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        throw ex;
                    }
                }
                catch (BeanCreationException ex) {
                    if (recordSuppressedExceptions) {
                        for (Exception suppressedException : this.suppressedExceptions) {
                            ex.addRelatedCause(suppressedException);
                        }
                    }
                    throw ex;
                }
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }

下面是我删减后只和循环依赖有关的代码

public Object getSingleton(String beanName, ObjectFactory<?> 
singletonFactory) {
    //getSingleton2 -1
    Object singletonObject = this.singletonObjects.get(beanName);
            //getSingleton2 -2
            if (singletonObject == null) {
                //getSingleton2 -3
                if (this.singletonsCurrentlyInDestruction) {
                    throw new Exception(beanName,
                            "excepition");
                }
                //getSingleton2 -4
                beforeSingletonCreation(beanName);
                //getSingleton2 -5
                singletonObject = singletonFactory.getObject(); 
            }
            return singletonObject;
        }

//getSingleton2 -1 开始解析

Object singletonObject = this.singletonObjects.get(beanName);

第二次getSingleton上来便调用了this.singletonObjects.get(beanName),直接从单例池当中获取这个对象,由于这里是创建故而一定返回null;singletonObjects是一个map集合,即所谓的单例池;用大白话说spring所有的单例bean实例化好都存放在这个map当中,这也是很多读者以前认为的spring容器,但是笔者想说这种理解是错误的,因为spring容器的概念比较抽象,而单例池只是spring容器的一个组件而已;但是你如果一定要找一个平衡的说法,只能说这个map——singletonObjects仅仅是狭义上的容器;比如你的原型bean便不在这个map当中,所以是狭义的spring容器;下图为这个map在spring源码当中的定义:

//getSingleton2 -2 开始解析

if (singletonObject == null) {
上面解释了,在spring 初始化bean的时候这里肯定为空,故而成立

//getSingleton2 -3 开始解析

if (this.singletonsCurrentlyInDestruction) {
            throw new Exception(beanName,
                    "excepition");
        }

这行代码其实比较简单,判断当前实例化的bean是否正在销毁的集合里面;spring不管销毁还是创建一个bean的过程都比较繁琐,都会先把他们放到一个集合当中标识正在创建或者销毁;所以如果你理解了前面那个正在创建集合那么这个正在销毁集合也就理解了;但是不理解也没关系,下面会分析这些集合;

如果一个bean正在创建,但是有正在销毁那么则会出异常;为什么会有这种情况?其实也很简单,多线程可能会吧;

//getSingleton2 -4 假设解析

beforeSingletonCreation(beanName);

这段代码就比较重要了,关于上面说那个正在创建和正在销毁的集合;这段代码就能解释,所以如果上面你没看明白那个集合的意义,笔者这里用spring源码来说明一下;先看看当代码执行到这里的时候语境:

当spring觉得可以着手来创建bean的时候首先便是调用beforeSingletonCreation(beanName);判断当前正在实例化的bean是否存在正在创建的集合当中,说白了就是判断当前是否正在被创建;因为spring不管创建原型bean还是单例bean,当他需要正式创建bean的时候他会记录一下这个bean正在创建(add到一个set集合当中);故而当他正式创建之前他要去看看这个bean有没有正在被创建(是否存在集合当中); 为什么spring要去判断是否存在这个集合呢?原因很多除了你们能想到了(你们能想到的基本不会出现,比如并发啊,重复创建什么的,因为他已经做了严格并发处理),其实这个集合主要是为了循环依赖服务的,怎么服务的呢?慢慢看吧,首先我们来看下这行代码的具体内容:

源码:

protected void beforeSingletonCreation(String beanName) {
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }

1、this.inCreationCheckExclusions.contains(beanName)这里是判断当前需要创建的bean是否在Exclusions集合,被排除的bean,程序员可以提供一些bean不被spring初始化(哪怕被扫描到了,也不初始化),那么这些提供的bean便会存在这个集合当中;一般情况下我们不会提供,而且与循环依赖无关;故而所以这里不做深入分析,后面文章如果写到做分析;

this.singletonsCurrentlyInCreation.add(beanName),如果当前bean不在排除的集合当中那么则这个bean添加到singletonsCurrentlyInCreation(当然这里只是把bean名字添加到集合,为了方便我们直接认为把bean添加到集合吧,因为他能根据名字能找打对应的bean);

关于singletonsCurrentlyInCreation的定义参考下图

其实就是一个set集合,当运行完this.singletonsCurrentlyInCreation.add(beanName) 之后结果大概如下图这样

我们可以通过debug来调试证明一下上面这幅图

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

结果分析:当代码运行完this.singletonsCurrentlyInCreation.add(beanName)之后可以看到singletonsCurrentlyInCreation集合当中只存在一个x,并且后天并没有执行x的构造方法,说明spring仅仅是把x添加到正在创建的集合当中,但是并没有完成bean的创建(因为连构造方法都没调用);

请一定注意这个集合的数据情况(目前只有一个x);因为这和循环依赖有天大的关系;add完x之后代码接着往下执行;

//getSingleton2 -5 开始分析

singletonObject = singletonFactory.getObject();
可能有读者已经忘记了singletonFactory这个对象怎么来的了;笔者再把代码贴一遍吧

ObjectFactory<?>  singletonFactory = new ObjectFactory(){
    public Object getObject(){
        //其实这是个抽象类,不能实例化
        //createBean是子类实现的,这里就不关心了
        //你就理解这不是一个抽象类吧
        AbstractBeanFactory abf = new AbstractBeanFactory();
        Object bean = abf.createBean(beanName, mbd, args);
        return bean;
    };
};
//传入 beanName 和singletonFactory 对象
sharedInstance = getSingleton(beanName,singletonFactory);

singletonFactory.getObject();调用的就是上面代码中getObject方法,换言之调用的是abf.createBean(beanName, mbd, args);把创建好的bean返回出来;至此第二次getSingleton方法结束,bean通过singletonFactory.getObject();调用createBean建完成;接下来分析createBean的源码,继续探讨循环依赖的原理;

AbstractAutowireCapableBeanFactory#createBean()方法中调用了doCreateBean方法创建bean;下图是dubug流程

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

结果分析:因为执行完doCreateBean之后X和Y的构造方法都已经完成了调用,说明这个方法里面对X做了实例化,也就是把bean创建好了,而且完成了循环依赖(因为Y的构造方法也打印说明X在完成属性注入的时候注入了Y,所以Y也实例化了,Y bean也创建好了)。

接下来重点分析这个doCreateBean方法内容。

好了,文章篇幅问题,接下来的内容,笔者再分一篇文章来分享。

另外,文章已在B站同步:子路玩Java,有兴趣的可以去看看,还有一些关于Spring的视频讲解,以及Spring循环依赖的面试回答方式。

如果大家觉得笔者写的还不错,或者感兴趣的话,可以点一个关注支持一下笔者!感谢大家支持!

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