Spring 系列之 Spring 源码笔记:bean 的加载-中【十】

接上篇文章:Spring 系列之 Spring 源码笔记:bean 的加载-上【九】

4. 获取单例

之前总结了从缓存中获取单例的过程,那么,如果缓存中不存在已经加载的单例 bean 就需要从头开始 bean 的加载,而 Spring 中使用 getSingleton 的重载方法实现 bean 的加载过程,代码如下:

/**
	 * 实现 bean 的加载过程
	 * 1.检查缓存是否已经加载过
	 * 2.若没有加载,则记录 beanName 的正在加载状态
	 * 3.加载单例前记录加载状态
	 * 4.通过调用参数传入的 ObjectFactory 的 getObject 方法实例化 bean
	 * 5.加载单例后移除缓存中对该 bean 的正在加载状态的记录
	 * 6.将结果记录至缓存并删除加载 bean 过程中所记录的各种辅助状态
	 * 7.返回处理结果
	 * Return the (raw) singleton object registered under the given name,
	 * creating and registering a new one if none registered yet.
	 * @param beanName the name of the bean
	 * @param singletonFactory the ObjectFactory to lazily create the singleton
	 * with, if necessary
	 * @return the registered singleton object
	 */
	public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		// 全局变量需要同步
		synchronized (this.singletonObjects) {
			// 首先检查对应的 bean 是否已经加载过, 因为 singleton 模式其实就是复用已创建的 bean, 所以这一步是必须的
			Object singletonObject = this.singletonObjects.get(beanName);
			// 如果为空才可以进行 singleton 的 bean 的初始化
			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 {
					// ******************* 初始化 bean ************************
					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;
		}
	}

上述代码中其实是使用了回调方法,使得程序可以在单例创建的前后做一些准备及处理操作,而真正的获取单例 bean 的方法其实并不是在此方法中实现的,其实现逻辑是在 ObjectFactory 类型的实例 singletonFactory 中实现的。而这些准备及处理操作在方法注释上写的很清楚。

步骤3说明: beforeSingletonCreation 方法有必要说一下,这个方法做了一个很重要的操作:记录加载状态,也就是通过 this.singletonsCurrentlyInCreation.add(beanName)将当前正要创建的 bean 记录在缓存中,这样便可以对循环依赖进行检测:
在这里插入图片描述
步骤5说明:同步骤3 的记录加载状态相似,当 bean 加载结束后需要移除缓存中对该 bean 的正在加载状态的记录:
在这里插入图片描述
虽然现在已经从外部了解了加载 bean 的逻辑架构,但现在还并没有开始对 bean 加载功能的探索,之前提到过,bean 的加载逻辑其实是在传入的 ObjectFactory 类型的参数 singletonFactory 中定义的,我们反推参数的获取,得到如下代码:
在这里插入图片描述
ObjectFactory 的核心部分其实只是调用了 createBean 的方法,所以需要到 createBean 方法中追寻真理。

5. 准备创建 bean

跟踪了这么多 Spring 代码和函数,我们知道不可能指望在一个函数中完成一个复杂的逻辑,或多或少也发现了一些规律:一个真正干活的函数其实是以 do 开头的,比如 doGetObjectFromFactoryBean;而给我们错觉的函数,比如 getObjectFromFactoryBean,其实只是从全局角度去做些统筹的工作。这个规则对于 createBean 也不例外,那么让我们看看在 createBean 函数中做了哪些准备工作。
在这里插入图片描述

5.1 处理 override 属性

查看源码中 AbstractBeanDefinition 类的 prepareMethodOverrides 方法:
在这里插入图片描述
在这里插入图片描述
之前反复提到过,在 Spring 配置中存在 lookup-method 和 replace-method 两个配置功能,而这两个配置的加载其实就是将配置统一存放在 BeanDefinition 中的 methodOverrides 属性里,这两个功能实现原理其实是在 bean 实例化的时候如果检测到存在 methodOverrides 属性,会动态地为当前 bean 生成代理并使用对应的拦截器为 bean 做增强处理,相关逻辑实现在 bean 的实例化部分详细介绍。

但是,这里要提到的是,对于方法的匹配来讲,如果一个类中存在若干个重载方法,那么,在函数调用及增强的时候还需要根据参数类型进行匹配,来最终确认当前调用的到底是哪个函数。但是,Spring 将一部分匹配工作在这里完成了,如果当前类中的方法只有一个,那么就设置重载该方法没有被重载,这样在后续调用的时候便可以直接使用找到的方法,而不需要进行方法的参数匹配验证了,而且还可以提前对方法存在性进行验证,正可谓一箭双雕。

5.2 实例化的前置处理

在真正调用 doCreate 方法创建 bean 的实例前使用了这样一个方法 resolveBeforeInstantiation(beanName, mbdToUse) 对 BeanDefinition 中的属性做些前置处理。当然,无论其中是否有相应的逻辑实现我们都可以理解,因为真正的逻辑实现前后留有处理函数也是可扩展的一种体现。但是,这并不是最重要的,在函数中还提供了一个短路判断,这才是最为关键的部分。
在这里插入图片描述
当经过前置处理后返回的结果如果不为空,那么会直接略过后续的 bean 的创建而直接返回结果。这一特性虽然很容易被忽略,但是却起着至关重要的结果,我们熟知的 AOP 功能就是基于这里的判断的:
在这里插入图片描述
此方法中最吸引我们的无疑是两个方法 applyBeanPostProcessorsBeforeInstantiation 以及 applyBeanPostProcessorsAfterInstantiation。两个方法的实现非常简单,无非是后处理器中的所有 InstantiationAwareBeanPostProcessor 类型的后处理器进行 postProcessBeforeInstantiation 方法和 BeanPostProcessor 的 postProcessAfterInitialization 方法的调用。

5.2.1 实例化前的后处理器应用

在这里插入图片描述
这个问题会在后面详细介绍,我们只需要知道,在 bean 的实例化前会调用后处理器的方法进行处理。

5.2.2 实例化后的后处理器应用

在这里插入图片描述

6. 循环依赖

实例化 bean 是一个非常复杂的过程,而其中的比较难理解的就是对循环依赖的解决。

6.1 什么是循环依赖

循环依赖就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 CircleB,CircleB 引用 CircleC,CircleC 引用 CircleA,则他们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用。

循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

6.2 Spring 如何解决循环依赖

Spring 容器循环依赖包括构造器循环依赖和 setter 循环依赖,在 Spring 中将循环依赖的处理分成了 3 种情况,下面进行介绍。

6.2.1 构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。

如在创建 TestA 类时,构造器需要 TestB 类,那将去创建 TestB,在创建 TestB 类时又发现需要 TestC 类,则又去创建 TestC,最终在创建 TestC 时发现又需要 TestA,从而形成一个环,没办法创建。

Spring 容器将每一个正在创建的 bean 标识符放在一个 “当前创建 bean 池” 中,bean 标识符在创建过程中将一直保持在这个池中,因此如果在创建 bean 过程中发现自己已经在 “当前创建 bean 池”里时,将抛出BeanCurrentlyInCreationException 异常表示循环依赖;而对于创建完毕的 bean 将从 “当前创建 bean 池” 中清除掉。

所以完整的分析如下:

  1. Spring 容器创建 TestA 的bean, 首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数 “testB”,并将“testA”标识符放到“当前创建 bean 池”;
  2. Spring 容器创建 TestB 的bean, 首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数 “testC”,并将“testB”标识符放到“当前创建 bean 池”;
  3. Spring 容器创建 TestC 的bean, 首先去“当前创建 bean 池”查找是否当前 bean 正在创建,如果没发现,则继续准备其需要的构造器参数 “testA”,并将“testC”标识符放到“当前创建 bean 池”;
  4. 到此为止 Spring 容器要去创建 “testA” bean, 发现该 bean 标识符在“当前创建 bean 池”中,因为表示循环依赖,抛出 BeanCurrentlyInCreationException 异常。

6.2.2 setter 循环依赖

表示通过 setter 注入方式构成的循环依赖。对于 setter 注入造成的依赖是通过 Spring 容器提前暴露刚完成构造器注入但未完成其它步骤(如 setter 注入)的bean 来完成的,而且只能解决单例作用域的 bean 循环依赖。通过提前暴露一个单例工厂方法,从而使其它 bean 能引用到该 bean,如下代码所示:

addSingletonFactory(beanName, new ObjectFactory(){
	public Object getObject() throws BeansException{
		return getEarlyBeanReference(beanName, mbd, bean);
	}	 
})

具体步骤如下:

  1. Spring 容器创建单例 “testA” bean, 首先根据无参构造器创建 bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的 bean,并将 “testA” 标识符放到 “当前创建 bean 池”,然后进行 setter 注入 “testB”。
  2. Spring 容器创建单例 “testB” bean, 首先根据无参构造器创建 bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的 bean,并将 “testB” 标识符放到 “当前创建 bean 池”,然后进行 setter 注入 “testC”。
  3. Spring 容器创建单例 “testC” bean, 首先根据无参构造器创建 bean,并暴露一个“ObjectFactory” 用于返回一个提前暴露一个创建中的 bean,并将 “testC” 标识符放到 “当前创建 bean 池”,然后进行 setter 注入 “testA”。进行注入“testA” 时由于提前暴露了“ObjectFactory” 工厂,从而使用它返回暴露一个创建中的 bean。
  4. 最后在依赖注入“testB” 和 “testA”,完成 setter 注入。

6.2.3 prototype 范围的依赖处理

对于“prototype”作用域bean,Spring 容器无法完成依赖注入,因为 Spring 容器不进行缓存 “prototype” 作用域的 bean,因此无法提前暴露一个创建中的 bean。

配置文件如下:

<bean id="testA" class="xxx.TestA" scope="prototype">
	<property name="testB" ref="testB"
</bean>
<bean id="testB" class="xxx.TestB" scope="prototype">
	<property name="testC" ref="testC"
</bean>
<bean id="testC" class="xxx.TestC" scope="prototype">
	<property name="testA" ref="testA"
</bean>

对于“singleton” 作用域bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用。

接下篇文章:Spring 系列之 Spring 源码笔记:bean 的加载-下【十一】

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值