IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring面试必问-循环依赖 -> 正文阅读

[Java知识库]Spring面试必问-循环依赖

1.什么是循环依赖

1.1概述

循环依赖就是不同的对象之间存在互相依赖的关系。比如说类A的构造器依赖B实例,B的构造器依赖A实例,那么Spring在初始化A实例时需要注入B实例,那么就需要从容器中拿B实例,这时就会去创建B实例,但是创建B实例又需要依赖A实例,。。。这时就出现了循环依赖的情况。(或者是多中对象之间的循环依赖 A依赖B | B依赖C | C依赖A等)

1.2代码举例

普通类A/B

/*
 *  两个普通的类A和B,A的构造器依赖B实例,B的构造器依赖A实例
 */
public class A {
	private A a;
	public A(B b) {
		this.a = a;
	}
}

public class B {
	private A a;
	public B(A a){
		this.a = a;
	}
}

Spring配置文件

<?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.xsd">

	
    <!-- 在Spring的配置文件中配置A B-->
    
	<bean id="a" class="com.shwsh.A">
        <!--构造器注入b-->
		<constructor-arg ref="b"></constructor-arg>
	</bean>

	<bean id="b" class="com.shwsh.B">
        <!--构造器注入a-->
		<constructor-arg ref="a"></constructor-arg>
	</bean>

</beans>

测试

public class Main {
	public static void main(String[] args) {
        /*
         *  初始化容器,因为A和B默认都是singleton,所以在容器启动时就会被创建
         */
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  

	}
}

运行结果

报错

//Spring检测出了循环依赖。
Is there an unresolvable circular reference

2.Spring存在哪几种循环依赖

Spring存在三种循环依赖

1.原型循环依赖

即原型模式对象A(即scope = prototype的bean)循环依赖了单例或者原型模式的的B

这种循环依赖Spring无法解决,只能抛出异常。

2.单例构造方法循环依赖

即1.2的代码示例,Spring无法解决,只能抛出异常

3.单例set注入循环依赖

Spring可以解决,通过三级缓存(下面详细讲解)提前暴露一个早期对象解决.

3.Spring源码层面如何检查出循环依赖

图示

3.1单例构造方法注入循环依赖检测

通过一个Set集合,里面记录的是当前正在创建**(未完成创建和初始化)**的beanName。

protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && 
            /*
             *1.这个条件成立,即set.add()失败,说明当前beanName对应的bean正处于创建中表示当前发生了循环依赖,直接抛出异常即可
             *2.条件不成立,说明当前beanName对应的bean未处于创建中,将当前beanName 
             *  并添加到set集合中。
             */
            !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

// singletonsCurrentlyInCreation 是一个Set集合
private final Set<String> singletonsCurrentlyInCreation =
      Collections.newSetFromMap(new ConcurrentHashMap<>(16));

bean完成创建和所有的初始化后,对应的beanName就会从set集合中移除

	protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && 
            //将创建成功的beanName从set集合中删除
            !this.singletonsCurrentlyInCreation.remove(beanName)) {
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}

3.2原型模式循环依赖检测

图示

这里的检测和3.1单例构造方法注入类似,不过这里使用的是一个ThreadLocal集合,里面存储了当前正在创建的beanName。

    try {
        //记录当前线程正在创建的原型对象的beanName
        beforePrototypeCreation(beanName);
        //创建对象
        prototypeInstance = createBean(beanName, mbd, args);
    } finally {
        //从正在创建中的集合中(HashSet)移除
        afterPrototypeCreation(beanName);
    }

beforePrototypeCreation

	protected void beforePrototypeCreation(String beanName) {
        //获取ThreadLocal内部的对象
		Object curVal = this.prototypesCurrentlyInCreation.get();
        //curVal == null,说明内部的value还没有初始化,直接存一个String即可
		if (curVal == null) {
			this.prototypesCurrentlyInCreation.set(beanName);
        //curVal有值 是String,此时还需要继续添加当前beanName,构造一个Set集合
		} else if (curVal instanceof String) {
			Set<String> beanNameSet = new HashSet<>(2);
			beanNameSet.add((String) curVal);
			beanNameSet.add(beanName);
			this.prototypesCurrentlyInCreation.set(beanNameSet);
        //curVal已经是Set集合了,直接add即可
		} else {
			Set<String> beanNameSet = (Set<String>) curVal;
			beanNameSet.add(beanName);
		}
	}

afterPrototypeCreation

	protected void afterPrototypeCreation(String beanName) {
		Object curVal = this.prototypesCurrentlyInCreation.get();
		if (curVal instanceof String) {
      		//curVal是一个String,直接调用ThreadLocal的remove()方法
            //将当前线程关联的ThreadLocal干掉。
			this.prototypesCurrentlyInCreation.remove();
		} else if (curVal instanceof Set) {
			Set<String> beanNameSet = (Set<String>) curVal;
            //移除当前beanName
			beanNameSet.remove(beanName);
			if (beanNameSet.isEmpty()) {
				this.prototypesCurrentlyInCreation.remove();
			}
		}
	}

原型循环依赖的检测过程

在getBean()方法执行的一个环节中**(先于创建单实例和原型实例的前面)**,会执行下面的代码,判断当前是否产生了原型循环依赖

			/*
			 *  原型模式循环依赖的判定(Spring无法处理,只能抛出异常)
			 *   (即原型模式对象A依赖一个B(B可能是原型也可能不是原型),B依赖A)
			 *     1.当前线程会在ThreadLoal中的Set集合中存储一个字符串"A",表示当前正在创建A对象
			 *     2.此时创建出来的A对象是一个早期对象
			 *     3.处理A的依赖,发现A依赖了B类型的对象,触发了getBean(B)的逻辑
			 *     4.根据B构造方法创建出B的早期实例
			 *     5.处理B的依赖,发现依赖了A
			 *     6.然后继续走上getBean("A")的逻辑,但是此时ThreadLocal的set集合中存在A字符串,说明`当前A正在创建中,表示				          	  							                   			                                  发生了循环依赖,此时直接抛出异常
			 * */

		     //返回true,表示当前beanName对应的bean正在创建中,说明发生了循环依赖直接抛异常
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}

isPrototypeCurrentlyInCreation

	    /*
		 * prototypesCurrentlyInCreation(ThreadLocal)
		 *  获取内部的set集合或者字符串,然后判断beanName是否存在。存在说明发生了循环依赖
         */
	protected boolean isPrototypeCurrentlyInCreation(String beanName) {
		//获取ThreadLoacl内部的String或者Set集合
		Object curVal = this.prototypesCurrentlyInCreation.get();
		
        /*
         * 1.当前ThreadLocal中存的还是一个String,那么就拿当前beanName进行对比
         * 2.当前ThreadLocal中存储的是一个Set集合,那么就调用contains()方法
         */
        return (curVal != null &&
				(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
	}

3.3总结

Spring检测循环依赖的核心就是使用一个Set集合存储当前正在创建的bean的beanName,假设A和B循环依赖,创建A实例时发现依赖B,将beanNameA存到Set集合中,然后执行getBean(B)的流程,也将beanNameB存到Set集合中,然后B依赖A走到getBean(A)的流程,但是发现Set集合中已经存在了beanNameA,那么说明发生了循环依赖,直接抛出异常。

4.Spring如何解决单例Set注入的循环依赖

4.1Spring的三级缓存

Spring解决单例Set注入循环依赖的核心就是使用三级缓存将创建出来的早期对象 (未完成后续注入和初始化) 包装成一个ObjectFactory放到三级缓存中去。

	/*
	*  一级缓存,key -> beanName : value创建出的单实例对象(经过完整生命周期)
	*  当单例bean对象被创建(完整对象)出来后,就会放到一级缓存中,下次getBean()时
	*  直接从缓存中获取。
	* */
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/*
	*  三级缓存,当一个单实例bean刚被创建出来时(还未完成初始化等后续操作),会将当前bean实例包装成一个ObjectFactory对象放到三级缓存。
	*          
	* */
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/*
	* 二级缓存 : 用来保存实例化完成,但是还未初始化完成的对象
	* key -> beanName : value -> Bean实例 
	* 当单实例bean正在创建过程中时,会先放到三级缓存中,然后其他线程调用getBean()时,
	* 从三级缓存中获取到了bean对象,然后就会将此bean从三级缓存中干掉,放到二级缓存中去
	* */
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

4.2源码-创建单例bean时放入三级缓存


//此时的bean是刚刚通过反射创建的一个早期对象,还未完成注入和初始化后处理器等操作。
final Object bean = instanceWrapper.getWrappedInstance();
····

// 是否需要提早曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
   if (logger.isDebugEnabled()) {
      //打印日志
   }
   // 为避免后期循环依赖,可以在bean初始化完成之前将创建实例的ObjectFactory加入工厂
   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) {
        if (!this.singletonObjects.containsKey(beanName)) {
			//放到三级缓存中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

4.3源码-getBean()时尝试先从缓存中获取

		/*
		 * getBean时()第二环节:从缓存中尝试获取对象
		 */
		Object sharedInstance = getSingleton(beanName);

getSingleton()

就是在进行创建对象前尝试先从缓存中获取,此时如果是单例set注入循环依赖,此时会从三级缓存中拿到早期对象,完成注入,然后走完整个创建流程,这样就解决了循环依赖。

   /*
	*  @param allowEarlyReference 是否允许早期引用 
	* 最终返回值情况
	*  1.一级缓存中找到,返回
	*  2.一级缓存没找到,二级缓存中找到 返回
	*  3.一二级缓存都没有,三级缓存(ObjectFactory)中找到,将三级缓存中的对象干掉放到二级缓存,并返回 
	*/
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {

		//从一级缓存中拿
		Object singletonObject = this.singletonObjects.get(beanName);
		/*
		*  满足下面的if条件:(一级缓存没有 并且 当前的bean对象正在创建中)
		*  Spring会将正在创建的bean的beanName加入到一个Set集合中。
		*
		*  条件一成立(singletionObject == null) 有几种可能?
		*  1.单实例确实未创建
		*  2.单实例正在创建中,当前发生了循环依赖
		*
		*  什么是循环依赖?  A依赖B,B依赖A。
 		*  单实例有几种循环依赖呢?
		*  1.构造方法循环依赖 (无解)
		*  2.set注入循环依赖  (有解,依靠三级缓存解决)
		*
		*  三级缓存如何解决set注入造成的循环依赖。
		*   举个例子 A -> B , B - A.
		*   1.假设Spring先实例化A,首先拿到A的构造方法,然后反射创建出A的早期对象,这时,
		*     这个早期对象被包装成了ObjectFactory对象放到了三级缓存中
		*	2.处理A的依赖数据,检查发现A依赖了B,所以接下来就走到getBean(B)的逻辑
		*   3.拿到B的构造方法然后反射创建早期实例对象,也会将B包装成ObjectFactory放到三级缓存中
		*   4.处理B的依赖数据,发现B依赖了A,所以接下来就会又走到getBean(A)去获得A对象,
		*   5.所以程序这时会走到当前这个getSingleton()方法
		*   6.走到这里的if判断,两个判断都会成立(1.A不在一级缓存中 2.A正在创建)
		* */
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			//从二级缓存中获取
			singletonObject = this.earlySingletonObjects.get(beanName);
			//二级缓存也没有 去三级缓存中查看。
			if (singletonObject == null && allowEarlyReference) {
				//加锁
				synchronized (this.singletonObjects) {

					/*
					*  Spring为什么需要有三级缓存存在,而不是只有二级缓存呢?
					*  AOP,靠动态代理实现。
					*  3级缓存在这里又什么目的呢?
					*  三级缓存中保存的是ObjectFactory,里面保存着原生的对象引用,getObject()方法需要考虑的是要返回
					*  原生对象还是增强后的对象,getObject()方法会判断当前这个早期实例是否需要被增强,如果需要的话,
					*  那么必须提前完成动态代理增强
					*  返回代理对象,否则返回原生对象。
					* */
                     
					//尝试继续从一级缓存中获取
					singletonObject = this.singletonObjects.get(beanName);
					//一级缓存中不存在
					if (singletonObject == null) {
						//再次尝试从二级缓存中获取
						singletonObject = this.earlySingletonObjects.get(beanName);
						//二级缓存也不存在
						if (singletonObject == null) {
							//从三级缓存中获取
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							//三级缓存不为NULL
							if (singletonFactory != null) {
								//获取包装的ObjectFactory中的早期对象
								singletonObject = singletonFactory.getObject();
								//将早期对象放到二级缓存中
								this.earlySingletonObjects.put(beanName, singletonObject);
								//将这个beanName对应的ObjectFactory从三级缓存中干掉
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

4.4总结

这也可以看出原型模式循环依赖和单例构造方法循环依赖为什么无法解决了。

  • 1.原型模式创建的早期对象不会放到三级缓存中去,所以无法通过上面的方法解决。
  • 2.单例构造方法循环依赖是在还没有将早期对象创建出来就发生循环依赖了,即早期对象根本就创建不出来,也不能通过上面的方式解决。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-28 22:46:11  更:2021-12-28 22:46:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:07:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码