当前位置: 首页 > news >正文

Spring源码-IOC容器(六)-bean的循环依赖

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

Spring IOC容器 源码解析系列,建议大家按顺序阅读,欢迎讨论

(spring源码均为4.1.6.RELEASE版本)

  1. Spring源码-IOC容器(一)-构建简单IOC容器
  2. Spring源码-IOC容器(二)-Bean的定位解析注册
  3. Spring源码-IOC容器(三)-GetBean
  4. Spring源码-IOC容器(四)-FactoryBean
  5. Spring源码-IOC容器(五)-Bean的初始化
  6. Spring源码-IOC容器(六)-bean的循环依赖
  7. Spring源码-IOC容器(七)-ApplicationContext
  8. Spring源码-IOC容器(八)-NamespaceHandler与自定义xml
  9. Spring源码-IOC容器(九)-Component-Scan源码解析
  10. Spring源码-IOC容器(十)-@Autowired解析

不知道大家有没有想过这样一种情况,在Spring的配置中,存在两个bean A和bean B,A依赖于B,B依赖于A,即A和B相互依赖(引用),xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
	
	<bean id="beanA" class="com.lntea.spring.demo.bean.BeanA">
		<property name="beanB" ref="beanB"></property>
	</bean>
	
	<bean id="beanB" class="com.lntea.spring.demo.bean.BeanB">
		<property name="beanA" ref="beanA"></property>
	</bean>
	
</beans>

BeanA.java

package com.lntea.spring.demo.bean;

public class BeanA {

	private BeanB beanB;
	
	public void print(){
		System.out.println("beanB:" + beanB + " beanA:" + beanB.getBeanA());
	}

	public BeanB getBeanB() {
		return beanB;
	}

	public void setBeanB(BeanB beanB) {
		this.beanB = beanB;
	}
	
	
}

BeanB.java

package com.lntea.spring.demo.bean;

public class BeanB {

	private BeanA beanA;

	public BeanA getBeanA() {
		return beanA;
	}

	public void setBeanA(BeanA beanA) {
		this.beanA = beanA;
	}
	
	
}

此时通过BeanFactory获取beanA,并调用print方法

BeanA beanA = beanFactory.getBean("beanA",BeanA.class);
beanA.print();

输出结果为

beanB:com.lntea.spring.demo.bean.BeanB@59a6e353 beanA:com.lntea.spring.demo.bean.BeanA@7a0ac6e3

可以看出beanA拿到了beanB的引用,beanB同时也拿到了beanA的引用。可见在Spring中是支持循环引用的,怎么实现的,有没有限制,我们再从源码来解析一下。

getBean方法首先会从缓存中查询是否存在创建好的单例

Object sharedInstance = getSingleton(beanName);

public Object getSingleton(String beanName) {
	return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// 查询缓存中是否有创建好的单例
	Object singletonObject = this.singletonObjects.get(beanName);
	// 如果缓存不存在,判断是否正在创建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		// 加锁防止并发
		synchronized (this.singletonObjects) {
			// 从earlySingletonObjects中查询是否有early缓存
			singletonObject = this.earlySingletonObjects.get(beanName);
			// early缓存也不存在,且允许early引用
			if (singletonObject == null && allowEarlyReference) {
				// 从单例工厂Map里查询beanName
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// singletonFactory存在,则调用getObject方法拿到单例对象
					singletonObject = singletonFactory.getObject();
					// 将单例对象添加到early缓存中
					this.earlySingletonObjects.put(beanName, singletonObject);
					// 移除单例工厂中对应的singletonFactory
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

从以上的代码可以看出

  1. 只针对单例的bean,多例的后面讨论
  2. 默认的singletonObjects缓存不存在要get的beanName时,判断beanName是否正在创建中
  3. 从early缓存earlySingletonObjects中再查询,early缓存是用来缓存已实例化但未组装完成的bean
  4. 如果early缓存也不存在,从singletonFactories中查找是否有beanName对应的ObjectFactory对象工厂
  5. 如果对象工厂存在,则调用getObject方法拿到bean对象
  6. 将bean对象加入early缓存,并移除singletonFactories的对象工厂

上面最重要的就是singletonFactories何时放入了可以通过getObject获得bean对象的ObjectFactory。根据我们的猜测,应该会是bean对象实例化后,而属性注入之前。仔细寻找后发现,在AbstractAutowireCapableBeanFactory类的doCreateBean方法,也就是实际bean创建的方法中,执行完createBeanInstance实例化bean之后有一段代码:

// bean为单例且允许循环引用且正在创建中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isDebugEnabled()) {
		logger.debug("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	// 创建ObjectFactory并添加到singletonFactories中
	addSingletonFactory(beanName, new ObjectFactory<Object>() {
		[@Override](https://my.oschina.net/u/1162528)
		public Object getObject() throws BeansException {
			return getEarlyBeanReference(beanName, mbd, bean);
		}
	});
}


protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	synchronized (this.singletonObjects) {
		// 判断默认缓存中没有beanName
		if (!this.singletonObjects.containsKey(beanName)) {
			// 添加ObjectFactory到singletonFactories
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

当判断bean为单例且正在创建中,而Spring允许循环引用时,将能获得bean对象的引用的ObjectFactory添加到singletonFactories中,此时就与之前的getSingleton方法相呼应。而allowCircularReferences标识在spring中默认为true,但是也可以通过setAllowCircularReferences方法对AbstractAutowireCapableBeanFactory进行设置。

再来看下getObject方法中的getEarlyBeanReference方法。这里也设置了一个InstantiationAwareBeanPostProcessor后置处理器的扩展点,允许在对象返回之前修改甚至替换bean。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				if (exposedObject == null) {
					return exposedObject;
				}
			}
		}
	}
	return exposedObject;
}

来梳理一下上面getBean("beanA")的执行过程

  1. 实例化BeanA
  2. 将能获取BeanA对象的ObjectFactory添加到singletonFactories中
  3. BeanA注入BeanB属性,调用getBean("beanB")方法
  4. 实例化BeanB
  5. 将能获取BeanB对象的ObjectFactory添加到singletonFactories中
  6. BeanB注入BeanA属性,调用getBean("beanA")
  7. 从singletonFactories中获取ObjectFactory并调用getObject方法拿到beanA对象的引用
  8. BeanB创建完成,注入到BeanA的beanB属性中
  9. BeanA创建完成返回

上面我们了解了单例的bean循环引用的处理过程,那么多例的呢?其实我们可以按上面的思路来思考一下,单例bean的循环引用是因为每个对象都是固定的,只是提前暴露对象的引用,最终这个引用对应的对象是创建完成的。但是多例的情况下,每次getBean都会创建一个新的对象,那么应该引用哪一个对象呢,这本身就已经是矛盾的了。因而spring中对于多例之间相互引用是会提示错误的。

// 如果已经存在多例的对象在创建中,就会抛出异常
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}

Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

可见spring会认为多例之间的循环引用是无法解决的。

转载于:https://my.oschina.net/u/2377110/blog/979226

相关文章:

  • Android 图片缓存处理
  • iOS中 OC字符串 与 C语言字符串 相互转换
  • php+Ajax 例子
  • 英语句型——口语
  • 三重积分计算--切片法
  • 关于梦想
  • 不用軟體解PPT密碼
  • 元组
  • activemq使用
  • 一个程序猿的遗嘱么?
  • Linux IO模式及 select、poll、epoll详解
  • bzoj 1600 amp; Usaco 月赛 2008 建造栅栏 题解
  • intellij idea svn 修改文件后,父文件夹也标注修改
  • VS 2015支持C语言和C++程序
  • springCloud(6):Eureka的自我保护模式、多网卡下的IP选择、Eureka的健康检查
  • 0x05 Python数据分析,Anaconda八斩刀
  • C++11: atomic 头文件
  • DOM的那些事
  • dva中组件的懒加载
  • E-HPC支持多队列管理和自动伸缩
  • Fastjson的基本使用方法大全
  • Git学习与使用心得(1)—— 初始化
  • JAVA多线程机制解析-volatilesynchronized
  • PermissionScope Swift4 兼容问题
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • python 装饰器(一)
  • ReactNativeweexDeviceOne对比
  • SQLServer之创建显式事务
  • vue总结
  • webgl (原生)基础入门指南【一】
  • 分享一份非常强势的Android面试题
  • 关于 Cirru Editor 存储格式
  • 判断客户端类型,Android,iOS,PC
  • 前嗅ForeSpider采集配置界面介绍
  • 悄悄地说一个bug
  • 容器化应用: 在阿里云搭建多节点 Openshift 集群
  • 数据可视化之 Sankey 桑基图的实现
  • 思考 CSS 架构
  • 小而合理的前端理论:rscss和rsjs
  • 用mpvue开发微信小程序
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • (7)STL算法之交换赋值
  • (附源码)springboot教学评价 毕业设计 641310
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (转)IOS中获取各种文件的目录路径的方法
  • (转)使用VMware vSphere标准交换机设置网络连接
  • (转载)从 Java 代码到 Java 堆
  • .“空心村”成因分析及解决对策122344
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置
  • .NET HttpWebRequest、WebClient、HttpClient