浅谈Spring中的循环依赖

什么是循环依赖?

    之前提到了在进行创建单例Bean的时候有个类参数singletonCurrentlyInCreation,这个参数是用来记录当前正在进行实例化的beanName。此外还定义了三个存放不同实例的缓存:

    一级缓存:singletonObjects,用来存放以beanName为key,对应已完成bean生命周期的bean实例作为value;

    二级缓存:earlySingletonObjects,里面存放的都是提前暴露的单例实例,此时bean的生命周期还未结束;

    三级缓存:singletonFactories:用来存放以beanName为key,对应的BeanFactory为value;

    我们先创建如图的两个类,除了各自引用外再添加一个参数,用来跟踪这个bean的生命周期进行到哪一步了,并重写toString()方法,如图1、图2:

图1
图2

    执行结果如图3,但是按照我们写的自定义Spring容器,会出现A和B不断的相互引用,最终耗尽了服务器资源,这就是循环依赖,Spring就是利用上面几个参数来解决循环依赖的问题。

图3

Spring如何解决循环依赖?

    1、在getBean操作的时候,进入doGetBean方法,执行getSingleton(beanName)方法,来尝试从三个缓存中拿到对应的实例,A_BeanName刚进来的时候三个缓存中都没有A_BeanName的值,在singletonCurrentlyInCreation中也没有正在实例化的beanName,说明目前这个bean还未被创建,如图4:

图4

    2、由于循环依赖只发生在单例的情况,那么直接看图5:

图5

    先关注下getSingleton操作,和上面获取实例的只是个同名方法而已,进来一共可以分成四部分,如图6:”beforeSingletonCreation(创建实例之前的操作)、getObject(创建实例)、afterSingletonCreation(创建实例之后的操作)、addSingleton(如果是新生成的单例,把他加进缓存)。beforeSingletonCreation会把当前的beanName放进singletonCurrentlyInCreation中,如图7。在调用getObject的时候会调用到匿名类的createBean方法来创建实例。

图6
图7

    3、关注doCreateBean,首先调用createBeanInstance,本次采用的是无参构造方法instantiateBean,把A包装成A_BeanWrapper,具体的封装情况参考上节的getBean流程。通过断点可以看到此时A_BeanWrapper已经包含了B,但是没有A的其他属性,B属性也为null,说明A并没有创建完成,此时B进行未进行依赖注入,如图8:

图8

    4、earlySingletonExposure参数代表是否能进行提前暴露,也可以从这看到提前暴露的条件:单例、允许循环依赖、当前beanName正处于正在创建Bean的列表中。如图9:

图9

    此时的getEarlyBeanReference方法是用来收集BeanFactory实例的,从实现方法里的getCacheKey中可以看到,工厂Bean前缀特有的&标识,如图10和图11:

图10
图11

    然后调用addSingletonFactory往三级缓存中添加A_BeanName和A_BeanFactory,如图12:

图12

    5、通过populateBean进行依赖注入,此时又是一个BeanPostProcessor的应用,postProcessProperties方法对@Autowired方法进行扫描,如图13:

图13

    findAutowiringMetadata返回的InjectionMetadata参数中的injectedElements参数就是B,如图14:

图14

    继续调用其inject方法,关注element.inject方法,由于@Autowired注解是放在参数上的,因此访问AutowiredFieldElement中的inject方法,调用resolveFieldValue,到beanFactory.resolveDependency,到doResolveDependency,到findAutowireCandidates方法获取到持有@Autowired注解的B,除了往autowiredBeanNames中添加对应的beanName外,调用descripter.resolveCandidate方法,进去发现是一个B_BeanName的getBean操作,如图15、16:

图15
图16

    6:、和A一样,重复之前的实例化流程,在singletonCurrentlyInCreation中添加B_BeanName,通过addSingletonFactory往三级缓存中添加B_BeanName和B_BeanFactory,然后通过populateBean操作,最终通过getBean方法对A进行实例化。

    7、又再次来到了第一步的getSingleton方法,此时singletonCurrentlyInCreation中已经有A_BeanName,虽然在一二级缓存中没有对应的实例,但是三次缓存中有A_BeanFactory,可以通过getObject拿到提前暴露的实例,然后把它放进二级缓存,打断点可以看到此时的A_SingletonObject中的B类属性为null,如图17::

图17

    但是此时走的就不是else的路线了,关注getObjectForBeanInstance方法,最终也是返回一个对象实例,如果是工厂实例,则会调用其getObject方法生成对象实例返回,期间并没有对其余属性进行诸如,我们在doGetBean的return的返回值中可以看到,如图18:

图18

    8、步骤2进行的getSingleton方法在getObject的方法终于有返回了,如图19,返回的是B的实例,可以看到生成的B实例中已经包含了testB属性,以及提前暴露的A属性。

图19

    然后再进行afterSingletonCreation方法,把B_BeanName从singletonCurrentlyInCreation中移除,如图20:

图20

    并通过addSingleton,把B_BeanName和B实例放入一级缓存后返回,此时A属性中的B属性依旧是null,如图21:

图21

    9、从上面的流程看,B实例化是由A的实例化中B属性的依赖注入触发getBean完成的,现在B已经被实例化了,因此A的B属性也可以完成依赖注入了,这样A中的B属性也有值。由于B的属性指向了A实例的堆空间,所以B类中的A属性也被赋予值了,如图22:

图22

    至此,Spring循环依赖的流程已经结束了。

    自定义Spring容器代码地址:https://github.com/LuoChen1996/identitify_spring.git

    Spring源码测试代码地址:https://github.com/LuoChen1996/my_spring.git

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

推荐阅读更多精彩内容