Java技术债务Java技术债务

  •  首页
  •  分类
  •  归档
  •  标签
  • 博客日志
  • 资源分享
  •  友链
  •  关于本站
注册
登录

Spring循环依赖产生原因以及解决的原理

Java

@[toc] 之前简单讲过Spring循环依赖的解决办法,但是没有深入源码分析,今天源码相关分析来了。

什么是循环依赖?

循环依赖问题就是A->B->A,spring在创建A的时候,发现需要依赖B,因为去创建B实例,发现B又依赖于A,又去创建A,因为形成一个闭环,无法停止下来就可能会导致cpu计算飙升

public class A {
	private B b;
}
public class B {
	private A a;
}

产生原因

如图所示 Spring循环依赖产生原因以及解决的原理 - Java技术债务

Spring的解决办法

为了解决此处闭环,重复循环创建依赖对象,添加三级缓存进行提前暴露对象

spring解决这个问题主要靠巧妙的三层缓存,所谓的缓存主要是指这三个map

  • singletonObjects主要存放的是单例对象,属于第一级缓存,可以说成品对象;
// Cache of singleton objects: bean name to bean instance. 
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  • earlySingletonObjects属于第二级缓存,作用是防止重复创建代理获取提前引用,并且防止循环依赖,可以说半成品对象;
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
  • singletonFactories属于单例工厂对象,属于第三级缓存
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

Spring循环依赖产生原因以及解决的原理 - Java技术债务

循环依赖解决的过程

首先画一个获取Bean简单流程 请添加图片描述 为什么先展示这个流程呢,因为在你去查看 Spring 解决循环依赖的整个源码的时候,你会发现会多次出现这个流程。

源码剖析

创建bean.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- 定义一个Bean -->
    <bean id="a" class="com.demo.test.A">
        <property name="b" ref="b"></property>
    </bean>
    <bean id="b" class="com.demo.test.B">
        <property name="a" ref="a"></property>
    </bean>
</beans>

测试类

package com.demo.test;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @Author cuizb
 * @Date 2022/1/18 13:50:43
 * @Desc ****
 */
@SpringBootTest
public class TestDemo {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        A a = (A) context.getBean("a");
    }
}

在获取xml文件处打上断点进行debug,此处是获取解析xml文件

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		loadBeanDefinitions(beanDefinitionReader);
	}

解析xml获的两个beanName,a和b Spring循环依赖产生原因以及解决的原理 - Java技术债务 getBean()

for (String beanName : beanNames) {
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				if (isFactoryBean(beanName)) {
					... ...
				} else {
					getBean(beanName);
				}
			}
		}

doGetBean()

@Override
public Object getBean(String name) throws BeansException {
	return doGetBean(name, null, null, false);
}

Spring循环依赖产生原因以及解决的原理 - Java技术债务

getSingleton(String beanName, boolean allowEarlyReference)方法就是从三级缓存中获取对象,第一次获取A对象时,容器中肯定没有,所以如上图所示,当前对象的值为null,那么接下来就是进行创建A对象。

/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}

再次调用getBean的重载方法getSingleton(String beanName, ObjectFactory<?> singletonFactory)进行将A对象工厂,放到三级缓存中,也就是map中,key=beanName,value=lambda表达式(createBean())

sharedInstance = getSingleton(beanName, () -> {
		try {
			return createBean(beanName, mbd, args);
		}
		catch (BeansException ex) {
			// Explicitly remove instance from singleton cache: It might have been put there
			// eagerly by the creation process, to allow for circular reference resolution.
			// Also remove any beans that received a temporary reference to the bean.
			destroySingleton(beanName);
			throw ex;
		}
	});	

之后会对A对象填充b属性值,然后从容器中寻找B,查不到会再次走一遍上述流程,然后存储B对象工厂到三级缓存中,在填充B对象a属性值时,会从容器中查找A对象,会将实例化的A对象从三级缓存删除 放在二级缓冲区,key=beanName,value=A对象的地址(A@1554),此时A对象在二级缓存中查到,然后对B对象中a属性赋值A对象的地址,此时B对象生成一个地址B@1558,然后进行初始化B对象,删除三级缓存和二级缓存,放到一级缓存,然后返回到A对象,将B对象的地址赋值给A对象中b属性,然后删除二级缓存,放到一级缓存中去,此时A对象已经完成实例化并且初始化对象。

在对B对象进行实例化时,会发现,B对象已经存在容器中一级缓存,直接获取到B对象。

Spring循环依赖产生原因以及解决的原理 - Java技术债务

总结

在实例化过程中,将处于半成品的对象地址全部放在缓存中,提前暴露对象,在后续的过程中,再次对提前暴露的对象进行赋值,然后将赋值完成的对象,也就是成品对象放在一级缓存中,删除二级和三级缓存。

如果不要二级缓存的话,一级缓存会存在半成品和成品的对象,获取的时候,可能会获取到半成品的对象,无法使用。

如果不要三级缓存的话,未使用AOP的情况下,只需要一级和二级缓存即可解决Spring循环依赖;但是如果使用了AOP进行增强功能的话,必须使用三级缓存,因为在获取三级缓存过程中,会用代理对象替换非代理对象,如果没有三级缓存,那么就无法得到代理对象

三级缓存时为了解决AOP代理过程中产生的循环依赖问题。

完
  • 本文作者:Java技术债务
  • 原文链接: https://cuizb.top/myblog/article/1642509311
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。
阅读全文
Java技术债务

Java技术债务

Java技术债务
Java技术债务
热门文章
  1. ClickHouse使用过程中的一些查询优化(六)2003
  2. MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据764
  3. MySQL主从同步原理458
  4. 线程池的理解以及使用414
  5. Spring Cloud Gateway整合nacos实战(三)409
分类
  • Java
    30篇
  • 设计模式
    27篇
  • 数据库
    20篇
  • Spring
    18篇
  • MySQL
    13篇
  • ClickHouse
    11篇
  • Kubernetes
    10篇
  • Redis
    9篇
  • Docker
    8篇
  • SpringBoot
    7篇
  • JVM
    6篇
  • Linux
    5篇
  • Spring Cloud
    5篇
  • 多线程
    5篇
  • Netty
    4篇
  • Kafka
    4篇
  • 面经
    4篇
  • Nginx
    3篇
  • JUC
    3篇
  • 随笔
    2篇
  • 分布式
    1篇
  • MyBatis
    1篇
  • 报错合集
    1篇
  • 生活记录
    1篇
  • 源码
    1篇
  • 性能优化
    1篇

最新评论

  • MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据2022-05-06
    Java技术债务:@capture 一起探讨学习,服务器被黑很正常,及时做好备份以及做好防护
  • MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据2022-04-13
    capture:我的刚上线两天,网站里就两篇文章也被攻击了,纳闷
  • Java常用集合List、Map、Set介绍以及一些面试问题2022-01-18
    Java技术债务:HashSet和TreeSet 相同点:数据不能重复 不同点: 1、底层存储结构不同; HashSet底层使用HashMap哈希表存储 TreeSet底层使用TreeMap树结构存储 2、唯一性方式不同 HashSet底层使用hashcode()和equal()方法判断 TreeSet底层使用Comparable接口的compareTo判断的 3、HashSet无序,TreeSet有序
  • undefined2021-12-14
    Java技术债务:如果不指定线程池,CompletableFuture会默认使用ForkJoin线程池,如果同一时间出现大量请求的话,会出现线程等待问题,建议使用自定义线程池。。。
  • undefined2021-12-02
    you:很好,对于小白相当不错了,谢谢
  • CSDN
  • 博客园
  • 程序猿DD
  • 纯洁的微笑
  • spring4all
  • 廖雪峰的官方网站
  • 猿天地
  • 泥瓦匠BYSocket
  • crossoverJie
  • 张先森个人博客
  • 越加网

© 2021-2022 Java技术债务 - Java技术债务 版权所有
总访问量 0 次 您是本文第 0 位童鞋
豫ICP备2021034516号
Java技术债务 豫公网安备 51011402000164号

微信公众号

Java技术债务
Java技术债务

专注于Spring,SpringBoot等后端技术探索

以及MySql数据库开发和Netty等后端流行框架学习

日志
分类
标签
RSS

有不足之处也希望各位前辈指出