Spring源码解析二十一(Spring如何通过三级缓存解决循环依赖)

我们之前已经研究了spring容器初始的主干线,接下来我们将要开始bean加载这条主线的研究。在bean加载的这条新的主线,我们会看到前面注册到spring容器中的BeanDefinition,是如何用来实例化bean的。

我们看下我们之前写的一段获取bean的代码:
在这里插入图片描述
可以看到这是之前我们分析ApplicationContext写的一段代码,其中bean的加载其实就是从getBean方法开始的,我们可以从getBean方法作为入口,开始分析spring 是如何加载bean的。我们先进到ApplicationContext的getBean方法中看下:
在这里插入图片描述
我们进入到AbstractApplicationContext的getBean方法中,在这个方法中有两行代码,我们先到assertBeanFactoryActive中看下:
在这里插入图片描述
在方法assertBeanFactoryActive中,只不过是判断当前的spring容器,是否被激活了或者是关闭了,如果容器还未被激活或发现容器已经关闭了就会抛出异常。如果spring容器关闭了或者还未初始化,也就没有办法加载bean了。
我们回到刚才的代码:
在这里插入图片描述
接下来就会通过getBeanFactory方法获取ApplicationContext中的spring容器初级容器BeanFactory,然后调用BeanFactory中的getBean方法去加载bean。所以,接下来我们到初级容器的getBean方法中看下:
在这里插入图片描述
我发现了一个比较关键的方法doGetBean,前面我们也提到过,也就是以do为前缀的方法差不多就是核心方法了,很明显方法doGetBean就是加载bean的核心方法的入口了。
接下来我们到doGetBean方法中看下:

	@SuppressWarnings("unchecked")
	protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		// 转换并得到bean的最终名称
		String beanName = transformedBeanName(name);
		Object bean;

		// Eagerly check singleton cache for manually registered singletons.
		// 根据bean的名称beanName,看下是否在单例缓存中已经注册了当前的bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) {
			if (logger.isTraceEnabled()) {
				if (isSingletonCurrentlyInCreation(beanName)) {
					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
							"' that is not fully initialized yet - a consequence of a circular reference");
				}
				else {
					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
				}
			}
			// 获取bean的示例对象
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}

		else {
			// 如果当前bean对象还没有被实例化,就对当前的bean进行实例化了
			// Fail if we're already creating this bean instance:
			// We're assumably within a circular reference.
			// 如果发现名称为beanName的bean正在创建,并且bean的类型为原型prototype抛出异常
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

			// Check if bean definition exists in this factory.
			// 获取当前spring容器的父容器
			BeanFactory parentBeanFactory = getParentBeanFactory();
			// 如果spring容器中还没有注册名称为beanName的BeanDefinition,并且父类容器存在就到spring容器的父容器中加载bean。
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				// Not found -> check parent.
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					// Delegation to parent with explicit args.
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					// No args -> delegate to standard getBean method.
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}

			if (!typeCheckOnly) {
				// 标记beanName 对应的bean已经开始创建了
				markBeanAsCreated(beanName);
			}

			try {
				// 将beanName 对应spring容器中的BeanDefinition合并封装成RootBeanDefinition类型的BeanDefinition
				RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
				// 检查下合并好的RootBeanDefinition是否达到了实例化bean的条件
				checkMergedBeanDefinition(mbd, beanName, args);

				// Guarantee initialization of beans that the current bean depends on.
				// 获取当前bean依赖的那些bean的名称数组
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
					for (String dep : dependsOn) {
						// 判断当前bean的名称和自己依赖的那些bean名称存在循环关系
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
						// 注册依赖的bean
						registerDependentBean(dep, beanName);
						try {
							// 递归调用getBean方法,提前实例化bean依赖的那些bean
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}

				// Create bean instance.
				// 如果当前BeanDefinition为单例类型的,则开始实例化创建单例singleton类型的bean
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							// 创建单例single类型的bean
							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的示例对象
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
				// 如果当前BeanDefinition为原型protoType类型的,则开始实例化原型protoType类型的bean
				else if (mbd.isPrototype()) {
					// It's a prototype -> create a new instance.
					Object prototypeInstance = null;
					try {
						// 实例化bean之前的处理
						beforePrototypeCreation(beanName);
						// 创建原型protoType类型的bean
						prototypeInstance = createBean(beanName, mbd, args);
					}
					finally {
						// 实例化bean之后的处理
						afterPrototypeCreation(beanName);
					}
					// 获取bean的示例对象
					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
				}

				else {
					// 创建其他作用域类型的bean
					String scopeName = mbd.getScope();
					if (!StringUtils.hasLength(scopeName)) {
						throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
					}
					// 获取作用域对象
					Scope scope = this.scopes.get(scopeName);
					// 如果没有为BeanDefinition设置scope则抛出异常
					if (scope == null) {
						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
					}
					try {

						Object scopedInstance = scope.get(beanName, () -> {
							// 实例化bean之前的处理
							beforePrototypeCreation(beanName);
							try {
								// 创建其他作用域类型的bean
								return createBean(beanName, mbd, args);
							}
							finally {
								// 实例化bean之后的处理
								afterPrototypeCreation(beanName);
							}
						});
						// 获取bean的示例对象
						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;
			}
		}

		// Check if required type matches the type of the actual bean instance.
		// 根据参数传进来的bean类型requiredType对实例化后的bean进行检查和转换
		if (requiredType != null && !requiredType.isInstance(bean)) {
			try {
				// 将bean转换为参数指定类型的bean
				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
				if (convertedBean == null) {
					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
				}
				return convertedBean;
			}
			catch (TypeMismatchException ex) {
				if (logger.isTraceEnabled()) {
					logger.trace("Failed to convert bean '" + name + "' to required type '" +
							ClassUtils.getQualifiedName(requiredType) + "'", ex);
				}
				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
			}
		}
		return (T) bean;
	}

可以看到,在这个doGetBean方法代码虽然很多,但是不管怎么样,Spring中bean加载的核心逻辑都在doGetBean方法中,接下来我们慢慢来分析这一块代码。现在我们开始看下doGetBean中代码逻辑吧:
在这里插入图片描述
首先调用了方法transformedBeanName来处理我们传进来的bean名称,为什么首先要处理bean的名称呢?我们到方法transformedBeanName中看下是如何处理的:
在这里插入图片描述
在transformedBeanName中,首先会通过容器的工具类BeanFactoryUtils调用rransformedBeanName方法来处理,我进到方法看下:
在这里插入图片描述
在BeanFactoryUtils的方法transformedBeanName中其实就是做了一件事情,如果发现name以一个或多个符号&为前缀,那就剔除掉name中的&,直到name前缀中的符号&都剔除干净了才返回name。那为什要这样做呢?我们在后分析相应的逻辑时候在看,接下来我们回到上一个方法:
在这里插入图片描述
已经获取到了剔除前缀符号&的name之后,可以看到,现在又把返回值交给了canonicalName处理,那canonicalName方法又会对name做怎样的处理呢?我们到方法里面看下:
在这里插入图片描述
可以看到,在方法canonicalName中,其实就是通过参数传进来的name,到别名缓存alilasMap中获取bean的实际名称,之前我们在BeanDefinition注册到spring容器之后,也看到了别名的注册逻辑。因为在别名缓存aliasMap中,存放了bean的别名stduent1到bean的实际名称student的映射,所以,在方法canonicalName中,就可以通过别名缓存aliasMap,根据别名student1获取bean的实际名称student,这样spring在拿着bean的实际名称student,就可以从spring容器中获取到bean的BeanDefinition了。

我们接着看下一个方法:
在这里插入图片描述

spring三级缓存解决循环依赖

接下来会调用方法getSingleton,根据方法getSingleton的名称我们可以知道,接下来应该是要获取beanName对应的单例bean了,我们到方法里面看下:
在这里插入图片描述
在方法getSingleton中又调用了它的重载方法getSingleton,并且在原来的参数基础上,又添加了一个新的参数allowEarlyReference,默认值为true。那参数alloEarlyReference有什么特别的作用吗?我们到方法中看下:
在这里插入图片描述
可以看到,方法中的代码逻辑比较紧凑,而且我们简单观察下可以发现,方法中用到了很多的成员变量如singletonObjects、earlySingletonObjects和singletonFactories,这些成员变量其实就是各种缓存。我们知道,现在缓存中现在是没有bean的实例的,因为我们现在是第一次调用getBean方法来实例化bean,大多数情况下的bean都是在用到时,才会触发bean的实例化,但是有没有特例呢?我们前面分析ApplicationContext初始化时,如果bean中的属性lazyInit的值设置为了false,就会提取调用getBean方法触发bean的实例化,也就是提前初始化这些非延迟加载的bean,而实例化好的单例对象就会被缓存在singletonObjects中。除了这种情况,前面我们还看到了spring内部的一些组件,如消息MessageSource、事件广播器ApplicationEventMulticaster等,在spring容器ApplicationContext初始化时就会实例化好,其实他们最后也是会注册到缓存singletonObjects中的,因为这些组件是spring功能实现的基础,必须在spring器初始化时就提前实例化好。

在getSinleton中,总共用到了三个缓存,我们看下这三个缓存是什么?
在这里插入图片描述
这三个缓存其实就是三个Map,分别用来缓存不同的数据,我们在回到方法getSingleton方法中:
在这里插入图片描述
首先会从singletonObjects中,根据bean的名称beanName来获取单例bean,singletonObjects这个缓存主要是用来存放已经完成实例化好的单例bean。为什么我们要强调singletonObjects是用来存放单例bean,并且是完全实例化好的单例bean呢?我们继续往后面看:
在这里插入图片描述
如果singletonObject 等于空,也就是singletonObjects中没有缓存名称为beanName的单例,并且方法isSingletonCurrentInCreation返回的是true。就会进入到分支里面,我们到方法isSingletonCurrentlyInCreation中看下,这个是在判断什么?
在这里插入图片描述
这个就是判断集合singletonsCurrentlyInCreation中是否存在beanName。我们在后面可以看到当bean开始实例化时,spring就会把bean对应的名称放入到集合singletonsCurrentlyInCreation中,表示这个bean正在实例化,防止bean重复进行实例化。当bean完成实例化完成后就会从集合singletonsCurrentlyInCreation中将bean的名称移除掉。

我们在回到getSingleton方法中继续来看下:
在这里插入图片描述
根据我们刚才的分析,如果在缓存singletonObject中找不到beanName对应的单例bean,同时beanName对应的单例正在实例化,接下来就会就会从缓存earlySingletonObjects中获取单例。那earlySingletonObjects又是什么呢?其实缓存earlySingletonObjects简单来说,就是用来存放早期单例的,早期单例就是bean的实例对象还没有全部实例化完成,仅仅只是通过反射创建了一个普通的bean对象出来,bean中的很多属性都还没有来得及赋值。为了满足其他地方的需要,就匆匆放到缓存earlySingletonObjects中了,这块逻辑后面我们也会看的的,所以刚才判断单例缓存中没有现成的单例bean,但是发现了bean正在实例化,我们就在试想能不能从缓存earlySingletonObjects中,提前获取实例化到一半的bean呢?那为什么一bean都还没有完全实例化完成,就要匆匆放到缓存earlySingletonObjects中,暴露给外界使用呢?这个还得说到spring实例化bean过程中可能会出现的一个问题,也就是循环依赖。

我们先通过一个案例来了解一下什么是循环依赖:
在这里插入图片描述
在这里插入图片描述
我们创建了两个类user1和user2,然后在这两个类中分别都创建了一个成员变量,user1的成员变量依赖user2,而user2的成员变量依赖user1。我们将这两个类配置到xml文件:
在这里插入图片描述
User1中的属性user2是依赖User2的,而User2中的属性user1是依赖User1的,所以,当User1实例化,为了给属性user2赋值就要得到实例User2。而User2在实例化时,同时也要为属性user1实例化User1,这样的话就会陷入一个无限循环了,而这就是spring中的循环依赖问题。

Spring是如何解决这个循环依赖的问题呢?Spring是通我们看到过的三级缓存来解决循环依赖的问题。我们继续回到我们之前的地方:
在这里插入图片描述
我们知道earlySingletonObjects其实就是用来缓存初步创建好的单例bean,bean中的很多属性都还没有来得及设置。可以看到如果我们从缓存earlySingltetonObject中获取到的singletonObject为空,并且allowEarlyReference为true,也就是允许使用早期单例bean,当然,参数allowEarlyReference默认值,我们前面已经看到了为为true,所以条件成立。
我们再来看下getSingleton方法:
在这里插入图片描述
当早期单例缓存earlySingletonObjects中也还找不到单例bean时,接下来就会寻找第三个缓存也就是singletonFactories,singletonFactories就是用来创建早期单例bean的工厂缓存,可以看到我们从singletonFactories中先获取到了bena的对象工厂singletonFactory。对象工厂singletonFactory,我们可以理解为是封装了早期单例bean实例化的方法,通过调用singletonFactory的getObject方法,我们可以获取到早期单例bean,也就是初始化到一半的 bean。Spring会将创建好的早期单例bean,在放到早期单例缓存earlySingletonObjects中,因为早期单例bean现在已经获取到了,这个时候对象工厂ObjectFactory也就没有存在的意义了,就会从工厂缓存singletonFactories中移除掉,最后将得到单例singletonObject直接返回了。

接下来,我们再来详细看下Spring是如何结合这三级缓存来解决循环依赖的。

Spring是怎样结合三级缓存来解决循环依赖的呢?

在Spring容器中,普通bean的实例化过程至少分为三个步骤:1、通过反射创建一个实例bean对象 2、为创建的bean对象填充属性 3、对这个bean进行一些初始化操作
为了方便我们理解下来的内容,我们先对 bean实例化过程有个初步的印象。我们来看下Spring是如何解决setter循环依赖的,我们根据我们之前中的案例User1和User2实例化过程进行分析,我们先看一张 流程图:
在这里插入图片描述
在user1在实例化时,首先会创建一个早期的单例bean,这个bean目前既没有设置属性值,也没有进行任何的初始化操作,也就是一个早期的单例bean。接着,因为user1在实例化是,需要依赖User2的实例为属性值,所以User1的实例化是依赖User2的实例。

如果按照上图中的方式,User1的实例在为自己的属性赋值时,就会触发User2的实例化,而User2在实例化到填充属性的阶段时,发现自己同样的也需要依赖User1的实例,此时就会陷入循环依赖的死循环里面了。

那有没有一种方式,既能让user1在实例时获取到User2的实例,同时User2又还没有开始为它的属性赋值,只要User2还没有为属性赋值,就不会出现循环依赖的问题了,User1不就可以先拿着User2的早期实例bean,先完成实例化。

当User2回过头来实例化为自己的属性赋值时,发现自己想要User1的实例,这个时候User1已经实例化完了,这样的话User2进行实例,就可以拿到User1的实例来完成属性值的赋值,循环依赖的问题不就决绝了吗?我们看下下面的流程

在这里插入图片描述
首先User2会把刚刚通过反射初步实例化好的早期单例bean,如上图先封装到对象工厂ObjectFactory中,并添加到单例工厂缓存singletonFactories里,这样的话,就相当于Spring把User2初步实例化的单例bean暴露到外界了。

User1此时如果要为属性赋值的话,就可以从工厂缓存中获取到User2的早期单例bean,完成属性赋值,进而完成User1的bean实例化,而User2的bean继续实例化,发现自己为属性赋值时也需要依赖User1的bean,这个时候User1的bean已经实例化好了,所以User2的实例化也可以完成了。

User1是如何从单例工厂缓存SingletonFactories中,获取User2的早期 实例的呢?如图:
在这里插入图片描述
User1的实例在实例化时,当User1的实例要为属性赋值时,就会通过工厂缓存singletonFactories,获取到User2的早期单例的对象工厂ObjectFactory,然后通过ObjectFactory的getObject方法获取到User2的早期的单例bean。

因为ObjectFactory存在的意义,就是在我们需要用到到期单例缓存bean时,才会去获取到早期单例bean,此时User2的对象工厂ObjectFactory,就可以从工厂缓存singletonFactories中移除掉了。

而当User1的实例正常实例化完成之后,User2的实例化也会继续实例化,最后把完全实例化好的完整单例bean放到单例缓存singletonObejects中,此时,完整的单例bean就已经得到了,那就没早期单例bean什么事情了,最后会把早期单例bean从早期单例缓存earlySingletonObject中给删除掉。

需要注意的是,以上只是Spring解决Setter注入循环依赖的方法,而构造方法循环依赖的问题Spring是没有办法解决的,原因也很简单的。因为Spring设计的这套三级缓存,就是通过提前暴露bean的早期单例bean,来解决setter注入的循环依赖,但是构造方法的循环依赖问题,是在反射创建bean时就会发生,此时Spring是没有办法提前获取早期单例bean的,因为早期单例bean要经过反射创建才能获取到。所以,对于构造器方法循环依赖,Spring是无法解决的。

我们在来看下前面的参数allowEarlyReference的含义,我们来看下代码:
在这里插入图片描述
可以看到,如果allowEarlyReference的值为false,也就是不允许引用早期的单例bean。如果参数为false的话,就无法通过工厂缓存singletonFactories获取到早期的单例bean,setter循环依赖就无法解决了。

  • Spring中的bean的作用域默认都是单例的,单例对象是要求全局唯一的,所以单例缓存singletonobjects的作用很简单,就是用来存放Spring容器全局唯一的单例bean。
  • Spring的earlySingletonObjects其实就是在bean还没有实例化完成时,临时用来存放早期单例的bean,因为这样的话,当前bean的实例化都需要依赖另外一个bean实例时,早期单例缓存earlySingletonObjects就可以为其他的bean的实例化提供依赖的bean实例了。
  • 第三级缓存的作用,主要还是提供一个懒加载的选择,毕竟通过ObjectFactory方法获取到早期的单例bean时,里面会有一些方法逻辑处理,所以,ObjectFactory存放缓存singletonFactories中的一个好处就是,可以让你在用到早期单例缓存时,才会调用ObjectFactor的方法来获取bean。

我们通过一张流程图总结下今天的内容:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

youngerone123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值