spring 源码系列(一)——spring 循环引用_shadow_s 的博客 - CSDN 博客_spring 循环引用

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

觉得之前那篇阅读性比价差,主要第一次用 csdn 博客,很多语法不懂,导致文章可读性不好,我彻底更新一下;打算把 spring 集合写完; 长文警告 正文开始

众所周知 spring 在默认单例的情况下是支持循环引用的

为了节省图片大小我把那些可以动得 gif 图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指点击图片便能看到动图,每张 gif 我都标识了,如果没有标识则为静态图片;

Appconfig.java类的代码

1
2
3
4
@Configurable
@ComponentScan("com.shadow")
public class Appconfig {
}

X.java类的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.shadow.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class X {

	@Autowired
	Y y;

	public X(){
		System.out.println("X create");
	}
}

Y.java 了的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.shadow.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Y {
	@Autowired
	X x;
	
	public Y(){
		System.out.println("Y create");
	}
}

这两个类非常简单,就是相互引用了对方,也就是我们常常的说的循环依赖,spring 是允许这样的循环依赖 (前提是单例的情况下的, 非构造方法注入的情况下)

运行这段代码的结果下图

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

https://img-blog.csdnimg.cn/20191118202529824.gif 上面代码从容器中能正常获取到Xbean,说明循环依赖成功。但是spring的循环依赖其实是可以关闭的,spring 提供了 api 来关闭循环依赖的功能。当然你也可以修改 spring 源码来关闭这个功能,这里笔者为了提高逼格,就修改一下 spring 的源码来关闭这个功能,老话说: 要想高明就得装逼。 下图是我修改 spring 源码运行的结果 我在 AnnotationConfigApplicationContext 的构造方法中加了一行setAllowCircularReferences(false);结果代码异常,循环依赖失败

https://img-blog.csdnimg.cn/20191118203015320.gif

那么为什么setAllowCircularReferences(false);会关闭循环依赖呢?首要明白 spring 的循环依赖是怎么做到的呢?spring 源码当中是如何处理循环依赖的? 分析一下所谓的循环依赖其实无非就是属性注入,或者就是大家常常说的自动注入, 故而搞明白循环依赖就需要去研究 spring 自动注入的源码;spring 的属性注入属于 spring bean 的生命周期一部分;怎么理解 spring bean 的生命周期呢?注意笔者这里并不打算对 bean 的生命周期大书特书,只是需要读者理解生命周期的概念,细节以后在计较; 要理解 bean 的生命周期首先记住两个概念 请读者一定记住两个概念——spring bean(一下简称 bean)和对象; 1、spring bean——受 spring 容器管理的对象,可能经过了完整的 spring bean 生命周期(为什么是可能?难道还有 bean 是没有经过 bean 生命周期的?答案是有的,具体我们后面文章分析),最终存在 spring 容器当中;一个 bean 一定是个对象 2、对象——任何符合 java 语法规则实例化出来的对象,但是一个对象并不一定是 spring bean;

所谓的 bean 的生命周期就是磁盘上的类通过 spring 扫描,然后实例化,跟着初始化,继而放到容器当中的过程; 我画了一张简单图来阐述一下 spring bean 的生命周期大概有哪些步骤

https://img-blog.csdnimg.cn/20191118210559319.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 上图就是 spring 容器初始化 bean 的大概过程 (至于详细的过程,后面文章再来介绍); 文字总结一下: 1:实例化一个 ApplicationContext 的对象; 2:调用 bean 工厂后置处理器完成扫描; 3:循环解析扫描出来的类信息; 4:实例化一个 BeanDefinition 对象来存储解析出来的信息; 5:把实例化好的 beanDefinition 对象 put 到beanDefinitionMap当中缓存起来,以便后面实例化 bean; 6:再次调用 bean 工厂后置处理器; 7:当然 spring 还会干很多事情,比如国际化,比如注册 BeanPostProcessor 等等,如果我们只关心如何实例化一个 bean 的话那么这一步就是 spring 调用finishBeanFactoryInitialization方法来实例化单例的 bean,实例化之前 spring 要做验证,需要遍历所有扫描出来的类,依次判断这个 bean 是否 Lazy,是否 prototype,是否 abstract 等等; 8:如果验证完成 spring 在实例化一个 bean 之前需要推断构造方法,因为 spring 实例化对象是通过构造方法反射,故而需要知道用哪个构造方法; 9:推断完构造方法之后 spring 调用构造方法反射实例化一个对象;注意我这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的 bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的 bean; 10:spring 处理合并后的 beanDefinition(合并?是 spring 当中非常重要的一块内容,后面的文章我会分析); 11:判断是否支持循环依赖,如果支持则提前把一个工厂存入 singletonFactories——map; 12:判断是否需要完成属性注入 13:如果需要完成属性注入,则开始注入属性 14:判断 bean 的类型回调 Aware 接口 15:调用生命周期回调方法 16:如果需要代理则完成代理 17:put 到单例池——bean 完成——存在 spring 容器当中

用一个例子来证明上面的步骤,结合一些运行时期的动态图片

为了节省图片大小我把那些可以动得 gif 图片做成了只循环一次,如果看到图片不动了请右键选择在新标签打开,那么图片就会动,手机用户则更简单,直接手指点击图片便能看到动图,每张 gif 我都标识了,如果没有标识则为静态图片;

Z.java 的源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.shadow.service;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class Z implements ApplicationContextAware {
	@Autowired
	X x;//注入X

    //构造方法
	public Z(){
		System.out.println("Z create");
	}

    //生命周期初始化回调方法
	@PostConstruct
	public void zinit(){
		System.out.println("call z lifecycle init callback");
	}

	//ApplicationContextAware 回调方法
	@Override
	public void setApplicationContext(ApplicationContext ac) {
		System.out.println("call aware callback");
	}
}

来看看 Z 的生命周期,注意下图当中的字幕,会和上面的 17 个步骤一一对应 下图是第一步到第六步,请自行对应 接下来我们通过各种图片分析一下 springbean 的生命周期,读者只需要看图搞明白流程,至于图中涉及的源码,分析完流程之后再来解释;

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

https://img-blog.csdnimg.cn/20191118223750129.gif 在研究其他步骤之前,首先了解 spring 大概在什么时候实例化 bean 的

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

https://img-blog.csdnimg.cn/20191119140401722.gif 上图可以知道 spring 在AbstractApplicationContext#finishBeanFactoryInitialization方法中完成了 bean 的实例化。这点需要记住

然后通过图片来说明一下第 7

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

https://img-blog.csdnimg.cn/20191119171834279.gif 接下来 spring 需要推断构造方法,然后通过推断出来的构造方法反射实例化对象,也就是上面说的 8 步和第 9

当然有可能推断不出来构造方法;关于这块知识博主后面更新文章

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

https://img-blog.csdnimg.cn/20191119205040133.gif 上图说明 spring 是通过createBeanInstance(beanName, mbd, args); 完成了推断构造方法和实例化的事情那么接下来便要执行第 10 步处理合并后的 beanDefinition 对象,这一块内容特别多,读者可以先不必要理解,后面文章会解释;

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

https://img-blog.csdnimg.cn/20191119210639833.gif 仔细看上图,其实这个时候虽然 Z 被实例化出来了,但是并没有完成属性的注入;其中的 X 属性为 null,而且里面的 Aware 接口的方法也没有调用,再就是@PostConstruct方法也没有调用,再一次说明他不是一个完整的 bean,这里我们只能说 z 是个对象; 继而applyMergedBeanDefinitionPostProcessors方法就是用来处理合并后的 beanDefinition 对象;

跟着第 11 ,判断是否支持循环依赖,如果支持则提前暴露一个工厂对象,注意是工厂对象

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

https://img-blog.csdnimg.cn/20191119213147922.gif12 步,spring 会判断是否需要完成属性注入(spring 默认是需要的,但是程序员可以扩展 spring,根据情况是否需要完成属性注入);如果需要则 spring 完成 13 步——属性注入,也就是所谓的自动注入;

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

https://img-blog.csdnimg.cn/20191119222408130.gif14、15、16

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

https://img-blog.csdnimg.cn/20191121140344401.gif 默认情况 至此一个 bean 完成初始化,被 put 到单例池,也是对上文说的 17 个步骤的一个证明;这说明一个 bean 在 spring 容器当中被创建出来是有一个过程的,这个过程就是所谓的 bean 的生命周期,我们的循环依赖也是在这个生命周内完成的。下面我们具体来分析这些步骤

由于 bean 的生命周期特别复杂本文只对涉及到循环依赖的步骤做分析,其他生命周期的步骤我会在后续博客中分析,可以继续关注博主

回顾上面的图②图③ 我们知道 spring 的 bean 是在AbstractApplicationContext#finishBeanFactoryInitialization()方法完成的初始化,即循环依赖也在这个方法里面完成的。该方法里面调用了一个非常重要的方法 doGetBean的方法

照例用图片来说明一下吧

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

https://img-blog.csdnimg.cn/20200318205324698.gif doGetBean方法内容有点多,这个方法非常重要,不仅仅针对循环依赖,甚至整个 spring bean 生命周期中这个方法也有着举足轻重的地位,读者可以认真看看笔者的分析。需要说明的是我为了更好的说清楚这个方法,我把代码放到文章里面进行分析;但是删除了一些无用的代码;比如日志的记录这些无关紧要的代码。下面重点说这个doGetBean方法

首先笔者把精简后的代码贴出来方便大家阅读

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
protected <T> T doGetBean(final String name, 
					@Nullable final Class<T> requiredType,
      				@Nullable final Object[] args, 
      				boolean typeCheckOnly)
      				throws BeansException {
    //读者可以简单的认为就是对beanName做一个校验特殊字符串的功能
    //我会在下次更新博客的时候重点讨论这个方法
    //transformedBeanName(name)这里的name就是bean的名字
   final String beanName = transformedBeanName(name);
   
   //定义了一个对象,用来存将来返回出来的bean
   Object bean;

	//deGetBean-1
   Object sharedInstance = getSingleton(beanName);
   
	//deGetBean-2
	if (sharedInstance != null && args == null) {
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }else{
   		deGetBean-3
   		if (isPrototypeCurrentlyInCreation(beanName)) {
         	throw new BeanCurrentlyInCreationException(beanName);
      }else{
      	//doGetBean-4
      	if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  return createBean(beanName, mbd, args);
               }
               catch (BeansException ex) {
                  destroySingleton(beanName);
                  throw ex;
               }
            });
            
      }
   }
 }

注意:上面的代码是我对 doGetBean 方法进行了删减的代码,只保留了和本文讨论的循环依赖有关的代码,完整版可以参考 spring 的源码org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

接着笔者对上述代码逐行来解释

1、deGetBean-1

Object sharedInstance = getSingleton(beanName); https://img-blog.csdnimg.cn/20200318211605581.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 首先这行代码上有一句 spring 作者写的注释

1
Eagerly check singleton cache for manually registered singletons.

大概的意思就是检查一下单例池当中有没有手动注册的单例对象,说白了 spring 在创建一个 bean 之前先检查一下beanName是否被手动注册过到单例池当中;别小看这句 spring 作者写的 javadoc 背后的意义,其实这里有两重意思;要搞清楚这两重意思首先知道当代码执行到这里的时候其实是 spring 在初始化的时候执行过来的;既然 spring 在初始化的时候他肯定知道这个类 X.java 肯定没有在容器当中,为什么还需要去验证一下呢?好比说你第一次去天上人间,你几乎都能确定这是你一次去你不可能跑到那里问一下前台你有没有办会员吧?但是 spring 确这样做了,他问了,他问问自己有没有办会员;为什么呢?回到你自己,如果你去问自己有没有办会员无非就是怕别人拿着你的身份证去办了一个会员,或者各种原因阴差阳错别人吧身份证名字写错了,导致你成了天上人间的会员;其实 spring 也是这个意思,因为一个 bean 被 put 到单例池的渠道有很多;除了 spring 容器初始化—扫描类 —- 实例化 —–put 到容器这条线之外还有很多方法可以把一个对象 put 到单例池;我这里只列举一种,其他的有机会再讨论,看下图 注意注释; https://img-blog.csdnimg.cn/2020031822353813.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 这就相当于在你第一次抱着紧张心态去天上人间的时候,发现你朋友以前拿着你的身份证去那里办了一个会员卡一样;

所以上面提到的这句注释的两重意思①第一重意思判断 spring 当前正准备初始化的 bean 有没有提前被 put 到容器; 那么第二重意思是什么呢?既然这里用来做 spring 初始化的工作,为什么这个方法名叫做 doGetBean 呢?讲道理应该叫做 createBean 啊才合理啊;有读者可能会说这个方法命名可能作者乱写的,请注意 spring 之所以经久不衰命名规范绝对是一个重要原因,作者是不会这么乱给方法命名的。诚然有的读者会说讨论这个的意义不大,其实博主觉得讨论这个非常重要;之所这里叫做 doGetBean 的原因就是因为这个方法就是用来获取 bean 的,他主要的工作不仅仅服务于 spring bean 的初始化;这个方法的作用不仅仅是为了 spring 在初始化 bean 的过程中去判断一下这个 bean 是否被注册了这么简单;笔者认为这个方法最主要的作用是为了从容器中得到一个 bean,也就是说当我们在 spring 代码中调用 getBean(“a”) 其背后的意义就是调用这个 doGetBean,同样用一段代码来证明

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

https://img-blog.csdnimg.cn/2020031822510331.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

1
2
3
//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

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

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

https://img-blog.csdnimg.cn/2020031823330061.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 比较简单我就不翻译了,一般情况下这里返回 false,也就是不会进入 if 分支抛异常;为什么呢说一般情况下呢?首先这里是判断当前的类是不是正在创建的原型集合当中,即里面只会存原型;一般情况下我们的类不是原型,而是单例的,大家都知道 spring 默认是单例;所以返回 false,再就是即使这个 bean 是原型也很少会在这里就存在 正在创建的原型集合 当中。因为不管单例还是原型,bean 在创建的过程中会 add 到这个集合当中,但是创建完成之后就会从这个集合 remove 掉(关于这个文章后面有证明),原型情况第一次创建的时候会 add 到这个集合,但是不是在这里,而是在后面的创建过程中 add,所以这里肯定不会存在,即使后面过程中 add 到这个集合了,但是创建完成之后也会 remove 掉,故而下一次实例化同一个原型 bean(原型可以实例化无数次)的时候当代码执行到这里也不可能存在集合当中了;除非循环依赖会在 bean 还没有在这个集合 remove 之前再次判断一次,才有可能会存在,故而我前面说了一般情况下这里都返回 false;那么单例情况我们已经说了一定返回 false,原型情况只有循环依赖才会成立,但是只要是正常人就不会对原型对象做循环依赖的;即使你用原型做了循环依赖这里也出抛异常(因为 if 成立,进入分支 throw exception)。再一次说明原型不支持循环依赖(当然你非得用原型做循环依赖,其实有办法,以后文章说明,本文忽略);画了一幅图说明上面的文字,因为这个集合非常重要,但是读者如果这里不理解也没关系,文章下面我还会结合代码分析一次;

https://img-blog.csdnimg.cn/20200319094127240.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70

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

下面解析 doGetBean-4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 是否单例;本文环境下是成立的;继而

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 源码贴出来,读者可以忽略这里,因为下面会精简代码;之所以贴出源码就是想告诉读者,为了研究循环依赖,本文中的很代码我是做了删减的;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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;
		}
	}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 开始解析

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 源码当中的定义

https://img-blog.csdnimg.cn/20200319100543544.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70

//getSingleton2 -2 开始解析

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

//getSingleton2 -3 开始解析

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

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

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

//getSingleton2 -4 假设解析

1
beforeSingletonCreation(beanName);

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

https://img-blog.csdnimg.cn/20200319105520107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70

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

https://img-blog.csdnimg.cn/20200319115650634.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 源码:

1
2
3
4
5
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的定义参考下图

https://img-blog.csdnimg.cn/20200319144654549.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 其实就是一个 set 集合,当运行完this.singletonsCurrentlyInCreation.add(beanName) 之后结果大概如下图这样

https://img-blog.csdnimg.cn/20200319150310482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 我们可以通过 debug 来调试证明一下上面这幅图

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

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

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

//getSingleton2 -5 开始分析

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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,如果你看着不动请参考我上面说的方法 **

https://img-blog.csdnimg.cn/20200319162830480.gif 结果分析:因为执行完 doCreateBean 之后 X 和 Y 的构造方法都已经完成了调用,说明这个方法里面对 X 做了实例化,也就是把 bean 创建好了,而且完成了循环依赖 (因为 Y 的构造方法也打印说明 X 在完成属性注入的时候注入了 Y,所以 Y 也实例化了,Y bean 也创建好了);接下来重点分析这个 doCreateBean 方法内容。

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

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

https://img-blog.csdnimg.cn/20200319170519702.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70

//doCreateBean -1

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,如果你看着不动请参考我上面说的方法 **

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

https://img-blog.csdnimg.cn/20200319174707219.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 这个 createBeanInstance 方法是如何把对象创建出来的呢?对应文章开头说的 bean 的生命周期一共 17 步,其中的第 8 步(推断构造方法)和第 9 步(利用构造方法反射来实例化对象);具体如何推断构造方法我会在后面的博客分析;这里截个图看看代码就行,不做分析;

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

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

https://img-blog.csdnimg.cn/2020031918085071.gif 至此 x 对象已经实例化出来,代码往下执行到合并 beanDefinition,看图吧

https://img-blog.csdnimg.cn/2020032011415039.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 但是其实合并 beanDefinition 和本文讨论的循环依赖无关,故而先跳过;

//doCreateBean-2 开始解析

1
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,那么代码接着往下执行;判断这个变量; https://img-blog.csdnimg.cn/20200320141219944.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 if成立,进入分支;

//doCreateBean-3 开始分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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 到哪里去了,查看源码 https://img-blog.csdnimg.cn/20200320151702208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 读者可以好好看看上图,笔者在 spring 源码当中把注释写上了(注释的信息很重要,认真看看),整个方法其实就是对三个 map 操作,至于这三个 map 的意义,参考下图 https://img-blog.csdnimg.cn/20200320145943343.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 通过代码可以得知 singletonFactory 主要被 add 到二级缓存中;至于为什么要 add 到这个 map?主要了循环依赖,提前暴露这个工厂;当然如果你不理解为什么要提前暴露,没关系往下看,看完文章一定会知道的;

保持好习惯照例画个图,让读者知道现在的情况吧 https://img-blog.csdnimg.cn/20200320152441259.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 当然这里还是用一幅图来秒杀一下这个三个 map 的各种情况吧

https://img-blog.csdnimg.cn/20200320170222591.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 一级缓存:可能存在很多 bean,比如 spring 各种内置 bean,比如你项目里面其他的已经创建好的 bean,但是在 X 的创建过程中,一级缓存中绝对是没有 xbean 的,也没用 y;因为 spring 创建 bean 默认的顺序是根据字母顺序的;

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

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

//doCreateBean-4 开始解析

1
populateBean(beanName, mbd, instanceWrapper);

populateBean 这个方法可谓大名鼎鼎,主要就是完成属性注入,也就是大家常常说的自动注入;假设本文环境中的代码运行完这行代码那么则会注入 y,而 y 又引用了 x,所以注入进来的 y 对象,也完成了 x 的注入;什么意思呢?首先看一下没有执行 populateBean 之前的情况 https://img-blog.csdnimg.cn/20200320154656495.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 没有执行 populateBean 之前只实例化了 X,Y 并没实例化,那么 Y 也不能注入了;接下来看看执行完这行代码之后的情况

https://img-blog.csdnimg.cn/20200320154854707.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 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 了;(读者可以对比一下上文的图)

https://img-blog.csdnimg.cn/20200320160609963.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 然后我们再把 xpy 到 ypx 的流程图贴出来,请读者仔细看看

https://img-blog.csdnimg.cn/20200320160912766.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 是否能够获取到 x 呢?首先我们想如果获取失败则又要创建 x—> 实例化 x—填充属性 —- 获取 y——–。。。。。。。就无限循环了;所以结果是完成了循环依赖,那么这里肯定能够获取到 x;那么获取到 x 后流程是怎样呢?

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

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

下面来开始对第一次 getSIngleton 源码做深入分析;首先把源码以及我写的注释贴出来,分为图片和源代码,建议大家看图片,可读性好 https://img-blog.csdnimg.cn/20200320174140426.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfbHl2ZWU=,size_16,color_FFFFFF,t_70 源码:如果你仔细看了上面的图片可以跳过这里的源码展示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 吧;

至此循环依赖的内容讲完,有错误欢迎指正,欢迎留言提问;如果觉得笔者写的对你有帮助可以多多点赞转发吧;