Spring IoC循环依赖问题

Spring IoC循环依赖问题

  • 什么是循环依赖
  • 循环依赖处理机制
    • 单例 bean 构造器参数循环依赖(⽆法解决)
    • prototype 原型 bean循环依赖(⽆法解决)
    • 单例bean通过setXxx或者@Autowired进⾏循环依赖
  • 循环依赖源码分析
    • (1)A实例化过程之后立马放入三级缓存(提前暴露自己)
    • (2)B对象实例化过程和A实例化过程一样,将自己放入到三级缓存中之后下一步进行属性注入发现依赖于A,那么就去三级缓存使用尚未成型的Bean A,并将A升级放到二级缓存中。
    • (3) 再次加工Bean A的过程以及放入二级缓存示意图
    • (4)B对象创建完成之后放入一级缓存中
  • 为什么需要三级缓存

什么是循环依赖

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
Spring IoC循环依赖问题_第1张图片
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。
Spring中循环依赖场景有:

  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)

其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法

循环依赖处理机制

单例 bean 构造器参数循环依赖(⽆法解决)

prototype 原型 bean循环依赖(⽆法解决)

对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。
AbstractBeanFactory.doGetBean()⽅法:

if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName); 
protected boolean isPrototypeCurrentlyInCreation(String beanName) { 
Object curVal = this.prototypesCurrentlyInCreation.get(); 
return (curVal != null &&
        (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) 
curVal).contains(beanName))));
}

在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记

try {
//创建原型bean之前添加标记 
beforePrototypeCreation(beanName); 
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args); 
}
finally {
//创建原型bean之后删除标记 
afterPrototypeCreation(beanName);
}

总结:Spring 不⽀持原型 bean 的循环依赖。

单例bean通过setXxx或者@Autowired进⾏循环依赖

Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中

循环依赖源码分析

Spring IoC循环依赖问题_第2张图片
Spring IoC循环依赖问题_第3张图片

(1)A实例化过程之后立马放入三级缓存(提前暴露自己)

refresh -> finishBeanFactoryInitialization-> getBean -> doGetBean -> createBean -> doCreateBean中

Spring IoC循环依赖问题_第4张图片

  • 判断A存在循环依赖,将A对象放入到三级缓存singletonFactories
	// 解决循环依赖的关键步骤,判断是否存在循环依赖:单例 && 允许循环依赖 && 当前bean正在创建中
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		// 如果需要提前暴露单例Bean,则将该Bean放入三级缓存中
		if (earlySingletonExposure) {
			if (logger.isDebugEnabled()) {
				logger.debug("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			// 将刚创建的bean放入三级缓存中singleFactories(key是beanName,value是FactoryBean)
			//如果支持则暴露一个工厂
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			/**
			 * 如果单例池当中不存在才会add
			 * 因为这里主要为了循环依赖服务的代码
			 * 如果bean存在单例池的话其实已经是一个完整的bean了
			 * 一个完整的bean自然也是已经完成属性注入,循环依赖已经依赖上了
			 * 所以如果这个对象已经是一个完整的bean,就不需要关心,不需要进入 if
			 * 如果一级缓存中没有该实例,则放入三级缓存中
			 */
			if (!this.singletonObjects.containsKey(beanName)) {
				// 放入三级缓存
				this.singletonFactories.put(beanName, singletonFactory);
				/**
				 * 从三级缓存中remove掉当前的bean
				 * 为什么要remove?抛开细节,这三个map当中其实其实存的都是一个对象
				 * spring的做法是三个不能同时存在
				 */
				// 从二级缓存中移除
				this.earlySingletonObjects.remove(beanName);
				//将beanName注册到registeredSingletons缓存(已经注册的单例集合)
				this.registeredSingletons.add(beanName);
			}
		}
	}
  • A对象中需要进行属性注入,发现依赖了B对象

Spring IoC循环依赖问题_第5张图片
Spring IoC循环依赖问题_第6张图片

(2)B对象实例化过程和A实例化过程一样,将自己放入到三级缓存中之后下一步进行属性注入发现依赖于A,那么就去三级缓存使用尚未成型的Bean A,并将A升级放到二级缓存中。

  • B对象实例化放入三级缓存中

Spring IoC循环依赖问题_第7张图片

  • B对象中需要进行属性注入,发现依赖了A对象

Spring IoC循环依赖问题_第8张图片
Spring IoC循环依赖问题_第9张图片

(3) 再次加工Bean A的过程以及放入二级缓存示意图

Spring IoC循环依赖问题_第10张图片

(4)B对象创建完成之后放入一级缓存中

Spring IoC循环依赖问题_第11张图片
(5)A对象从一级缓存中获取到B对象,注入到A对象中
Spring IoC循环依赖问题_第12张图片

为什么需要三级缓存

Spring循环依赖以及为什么需要三级缓存

你可能感兴趣的:(#,Spring,spring,java,原型模式)