spring---单例Bean循环依赖

  假设当前有两个单例Bean,A,B相互依赖在创建时都需要对方。spring默认是允许这种情况存在的,至于是怎么解决循环依赖问题的下面一起看一下。

 循环依赖功能默认是开启的同时spring 给我们提供了方法可以手动控制它,在AbstractAutowireCapableBeanFactory # setAllowCircular References方法设置为false时就可以关闭循环依赖。从下面可以看到allowCircularReferences 变量默认是true。

private boolean allowCircularReferences = true;
public void setAllowCircularReferences(boolean allowCircularReferences) {
	this.allowCircularReferences = allowCircularReferences;
}

 为什么setAllowCircularReferences(false)会关闭循环依赖,下面我们就得去看看源码中循环依赖问题是如何解决的。首先我们需要跟踪bean创建的生命周期一步步看到底是在哪里进行了处理bean创建的具体过程这里不细说下面是一个简单的调用过程:

1.AbstractApplicationContext#refresh() 
2.AbstractApplicationContext#finishBeanFactoryInitialization(beanFactory)
3.DefaultListableBeanFactory#preInstantiateSingletons()
4.AbstractBeanFactory#getBean
5.AbstractBeanFactory#doGetBean
6.DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory) // 这里涉及到循环依赖问题
7.AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])
8.AbstractAutowireCapableBeanFactory#doCreateBean(beanName, mbdToUse, args)
9.AbstractAutowireCapableBeanFactory#createBeanInstance(beanName, mbd, args) // 完成了推断构造方法和实例化的事情
10.AbstractAutowireCapableBeanFactory#populateBean(beanName, mbd, instanceWrapper)

 首先进入到第6步我们知道在创建bean之前先会尝试到单例池中拿,如果缓存中没有才创建

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(beanName, "Bean name must not be null");
		synchronized (this.singletonObjects) {
			// private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
			Object singletonObject = this.singletonObjects.get(beanName); // 第一部先到单例缓存池拿,此时A还没有创建返回bull
			if (singletonObject == null) {
				// 会把正在创建的bean的name 放入到这个集合
				// private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
				beforeSingletonCreation(beanName); // 将当前正在创建的bean放入singletonsCurrentlyInCreation集合
				try {
					// 初始化bean这个过程其实是调用createBean方法
				
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}

将当前正要创建的bean名字放入到此集合

protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

createBean方法中又调用doCreateBean方法开始正式初始化bean实例

if (instanceWrapper == null) {
			// 不是factoryBean的话就会进入到这一步
			// 推断构造函数使用合适的实例化策略来创建BeanWrapper对象,此时的bean称为早期对象。虽然实例化了,但是属性还没有注入。
			// 假设当前实例化的是A,那么A在次数已经创建但是B的属性还没有注入
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		
...... 不是很相关的代码我都直接省略掉了

// 单例 && 允许循环引用 && singletonsCurrentlyInCreation中存放当前正在创建的bnen的名字,这里判断当前bean是否正在创建
// 在前面代码中已经将当前正在创建的beanName放入到了此集合中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName)); 
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			// 提前暴露早起对象,加入到缓存中
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

具体实现在:DefaultSingletonBeanRegistry#addSingletonFactory()

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) { 
			if (!this.singletonObjects.containsKey(beanName)) { // 先从单例缓存池中找,false取反为true继续往下执行
				// 这里就将当前A工厂对象放入到了三级缓存中
				this.singletonFactories.put(beanName, singletonFactory); 
				// 三级缓存中已经存在二级缓存没有存在必要此处进行删除
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}

 接下来将进行关键的属性注入,我们知道前面只是创建了A的对象但是并没有设置属性,这里就到了解决循环依赖的关键。在为A设值的时候需要实例化B但是B又依赖于A可是此时我们的A还没有完全创建完。继续往下看进入到populateBean方法看看spring是如何解决的

 在为A进行属性注入的时候必须要B,此时我们去获取B的时候就会进行创建从这里开始就需要再次重复上面A创建的的过程。直到进行需要调用populateBean为B属性注入值时,此时又循环回来需要获取A实例。会再次进入getSingleton

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 此时从单例池中拿A,因为A还没有创建完成所以获取不到,返回null
		Object singletonObject = this.singletonObjects.get(beanName);
		// 直接看第二个条件,isSingletonCurrentlyInCreation判断当前A是否正在创建前面已经提到过在A创建是已经将BeanName加入到此集合中这里成立所以可以继续往下执行
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				// 先从三级缓存拿A,拿不到因为上文提到A工厂对象是被放在2级缓存中的
				singletonObject = this.earlySingletonObjects.get(beanName);
				// 这里null成立,allowEarlyReference文章开头说过默认为true
				if (singletonObject == null && allowEarlyReference) {
					// 此时成功从二级缓存中拿到A的工厂对象
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					//由于获取到了,进入if分支
					if (singletonFactory != null) {
						//调用工厂对象的getObject()方法,产生一个A的半成品bean
						singletonObject = singletonFactory.getObject();
						//拿到了半成品的A之后,把他放到三级缓存
						this.earlySingletonObjects.put(beanName, singletonObject);
						//然后从二级缓存清除掉A的工厂对象
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
		}

 到这里就豁然开朗了此时B获取到了A对象就可以完成属性注入,而A对象也能继续执行下去将B注入后完成创建。

这里为什么首先去三级缓存中找?
 是三级缓存中存的是一个A对象,如果能取到则不去二级找了。

二级缓存的作用又是什么呢,为什么不直接存放在3级缓存中?
 二级缓存存放的是一个工厂对象而不是真实对象是为应对属性变更的情况,如果直接存放对象就写死了。比如说aop的情况下A注入B,B也注入A;而B中注入的A需要加代理(aop),但是加代理的逻辑在注入属性之后,也就是A的生命周期到注入属性的时候A还不是一个代理对象,那么这个时候把A存起来,然后注入B,获取、创建B,B注入A,获取A;拿出来的A是一个没有代理的对象;但是如果存的是个工厂就不一样;首先把一个能产生A的工厂存起来,然后注入B,注入B的时候获取、创建B,B注入A,获取A,先从三级缓存获取,为null,然后从二级缓存拿到一个工厂,调用工厂的getObject();spring在getObject方法中判断这个时候x被aop配置了故而需要返回一个代理的A出来注入给B。

为什么要从二级缓存remove?
 因为如果存在比较复杂的循环依赖可以提高性能;比如A,B,C相互循环依赖,那么第一次B注入A的时候从二级缓存通过工厂返回了一个A,放到了三级缓存,而第二次C注入A的时候便不需要再通过工厂去获得A对象了。因为if分支里面首先是访问三级缓存;至于remove则是为了gc吧。

构造器循环依赖:
 因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

构造器循环依赖解决办法:
 在构造函数中使用@Lazy注解延迟加载。在注入依赖时,先注入代理对象,当首次使用时再创建对象说明:一种互斥的关系而非层次递进的关系,故称为三个Map而非三级缓存的缘由完成注入。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值