本篇文章为你整理了Spring循环依赖产生原因以及解决的原理(spring循环依赖到底怎么解决的)的详细内容,包含有spring循环依赖产生原因以及解决的原理是什么 spring循环依赖到底怎么解决的 spring循环依赖是什么 spring循环依赖的三种方式 Spring循环依赖产生原因以及解决的原理,希望能帮助你了解 Spring循环依赖产生原因以及解决的原理。
文章目录
什么是循环依赖?产生原因Spring的解决办法循环依赖解决的过程源码剖析总结更多文章和干货请关注公众号
之前简单讲过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的解决办法
为了解决此处闭环,重复循环创建依赖对象,添加三级缓存进行提前暴露对象
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);
循环依赖解决的过程
首先画一个获取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"
property name="b" ref="b"/
/bean
bean id="b"
property name="a" ref="a"/
/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 contexts
// 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
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);
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对象。
总结
在实例化过程中,将处于半成品的对象地址全部放在缓存中,提前暴露对象,在后续的过程中,再次对提前暴露的对象进行赋值,然后将赋值完成的对象,也就是成品对象放在一级缓存中,删除二级和三级缓存。
如果不要二级缓存的话,一级缓存会存在半成品和成品的对象,获取的时候,可能会获取到半成品的对象,无法使用。
如果不要三级缓存的话,未使用AOP的情况下,只需要一级和二级缓存即可解决Spring循环依赖;但是如果使用了AOP进行增强功能的话,必须使用三级缓存,因为在获取三级缓存过程中,会用代理对象替换非代理对象,如果没有三级缓存,那么就无法得到代理对象
三级缓存时为了解决AOP代理过程中产生的循环依赖问题。
更多文章和干货请关注公众号
本文作者:Java技术债务
原文链接:https://www.cuizb.top/myblog/article/1642509311
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。
以上就是Spring循环依赖产生原因以及解决的原理(spring循环依赖到底怎么解决的)的详细内容,想要了解更多 Spring循环依赖产生原因以及解决的原理的内容,请持续关注盛行IT软件开发工作室。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。