• Spring实例化源码解析之循环依赖CircularReference(十三)


    前言

    首先什么是循环依赖,简单说就是互相引用。在Spring中是默认支持循环依赖的,至于怎么解决的循环依赖问题,就是本章要探讨的内容。

    // 默认允许循环依赖
    private boolean allowCircularReferences = true;
    
    //提供set方法
    public void setAllowCircularReferences(boolean allowCircularReferences) {
        this.allowCircularReferences = allowCircularReferences;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    准备

    需要创建两个类,他们需要和下图所示一样的进行循环依赖。

    在这里插入图片描述

    创建两个最简单的TestA对象和TestB对象,代码如下:

    @Service
    public class TestA {
    
    	@Autowired
    	TestB testB;
    }
    
    @Service
    public class TestB {
    	@Autowired
    	TestA testA;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    TestA和TestB的实例化流程

    Bean的实例化这章讲述了一般bean的实例化流程,所以直接在DefaultListableBeanFactory的preInstantiateSingletons方法开始,打一个条件断点来逐步分析。

    在这里插入图片描述

    我们创建的TestA是单例对象,并且不是FactoryBean,也无需lazyInit,所以下一步我们直接去doGetBean。

    doGetBean(testA)

    到达doGetBean方法,会去从一级缓存中获取,此时肯定是没有的,所以直接返回null。继续执行后续else中的逻辑。

    在这里插入图片描述

    getSingleton(testA,testAObjectFactory)

    else逻辑中会进入到getSingleton这个带有函数式接口的方法。在这里我将传入的函数式接口命名为testAObjectFactory,为了方便描述,实际代码如下

    sharedInstance = getSingleton(beanName, () -> {
    						try {
    							return createBean(beanName, mbd, args);
    						}
    						catch (BeansException ex) {
    							destroySingleton(beanName);
    							throw ex;
    						}
    					});
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    getSingleton这个方法会有一个小操作beforeSingletonCreation(beanName),就是将当前准备创建的testA放入singletonsCurrentlyInCreation,也就是标记这个对象正在创建中。

    在这里插入图片描述

    标记完testA对象之后调用testAObjectFactory,就是doCreateBean的逻辑了。

    doCreateBean(testA)

    在做创建Bean的逻辑时,有一个属性earlySingletonExposure,翻译过来就是早期的单例暴露。而是否需要早期暴露单例对象是通过allowCircularReferences、singletonsCurrentlyInCreation和testA是否是单例来判断的。所以这也就能知道为什么默认允许循环引用和为什么要先标记对象正在创建中了。

    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));
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    而这里的早期暴露就是大家所知道的提前曝光,核心就是addSingletonFactory。会把传入的函数式接口存在二级缓存中,如下图所示:

    在这里插入图片描述

    populateBean(testA)

    然后使用AutowiredAnnotationBeanPostProcessor的postProcessProperties方法来实际进行属性值的注入

    在这里插入图片描述

    获取到Field之后通过beanFactory.getBean(testB)来继续创建TestB来满足TestA的实例化。

    在这里插入图片描述

    doGetBean(testB)

    testB当前也没有实例化过,所以这边从一级缓存中也是获取不到的,直接返回null进入else逻辑

    在这里插入图片描述

    getSingleton(testB,testBObjectFactory)

    getSingleton方法里会把testB标记为正在创建中。然后执行testBObjectFactory函数式接口,也就是createBean(testB)

    在这里插入图片描述

    在这里插入图片描述

    doCreateBean(testB)

    同样testB也满足提前曝光的要求,所以会把testB也放入到三级缓存中

    在这里插入图片描述

    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);
    			}
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    populateBean(testB)

    然后继续进行属性输入,因为testB依赖了TestA,所以会执行beanFactory.getBean(testA),所以直接进入doGetBean方法。

    doGetBean(testA)

    进度doGetBean方法之后就是直接getSingleton(testA)

    protected <T> T doGetBean(
    			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    			throws BeansException {
    
    		String beanName = transformedBeanName(name);
    		Object beanInstance;
    
    		// Eagerly check singleton cache for manually registered singletons.
    		// !!!!!!!!!!!!!!!!!三级缓存的核心
    		Object sharedInstance = getSingleton(beanName);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    getSingleton(testA)

    因为testA对象在前面将其标记为正在创建中了,并且一级缓存当前还没有testA实例对象,因为testA这个时候还没有实例化完成。所以三级缓存中存在testA的ObjectFactory对象。

    在这里插入图片描述

    回忆一下我们的提前曝光放入的内容

    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    
    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    		Object exposedObject = bean;
    		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
    				// 获取早期bean得引用
              				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
    			}
    		}
    		return exposedObject;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    所以在singletonFactory调用getObject方法的时候会执行这个getEarlyBeanReference方法来获取对象,将获取到的对象放入二级缓存,并将三级缓存情况,此时三级缓存就只有testB了。getEarlyBeanReference这个方法是用来判断对象是否需要被代理,如果不需要会返回原始bean。

    在这里插入图片描述

    initializeBean(testB)

    在这里插入图片描述

    getSingleton(testB,testBObjectFactory)

    执行完testObjectFactory函数式接口之后,DefaultSingletonBeanRegistry中的getSingleton(String beanName, ObjectFactory singletonFactory)方法会继续执行。

    在这里会把testB放入到一级缓存中,并且移除二三级缓存。

    protected void addSingleton(String beanName, Object singletonObject) {
    		// 线程安全的
    		synchronized (this.singletonObjects) {
    			// 放入一级缓存
    			this.singletonObjects.put(beanName, singletonObject);
    			// 移除三级缓存
    			this.singletonFactories.remove(beanName);
    			// 移除二级缓存
    			this.earlySingletonObjects.remove(beanName);
    			// 已注册的map中把新初始化的bean放入
    			this.registeredSingletons.add(beanName);
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    initializeBean(testA)

    在这里插入图片描述

    getSingleton(testA,testAObjectFactory)

    同样执行完initializeBean(testA)之后会把testA放入到一级缓存

    怎么解决的?

    首先我们经过上述一步一步的断点方式,了解整体的运行方式,这边使用一张图来进行概括。

    在这里插入图片描述

    从图中可以看出提前曝光是解决循环依赖的核心。而allowCircularReferences、singletonsCurrentlyInCreation就是辅助其工作,让其在循环依赖的情况下能正常运作。

    为什么是三级缓存?

    很遗憾,我也曾被问到过这个三级缓存的面试题,面试官问过我为什么是三级而不是二级。这个问题其实就是问一下你对spring三级缓存的个人理解。

    以下是个人见解,仅供参考:

    一级缓存singletonObjects,这个毫无疑问得要。所以如果是二级缓存的话,我们必须在二级缓存和三级缓存中做取舍。

    假设二级缓存没有,也就是earlySingletonObjects这个缓存不存在。当发生循环依赖的时候,会出现对象不一致的问题。举例如下:

    假设A中注入B和C,B中注入A,C中也注入A。

    如果没有二级缓存,B中和C中的A可能不是同一个。这种情况就会出现不一致的问题,所以earlySingletonObjects得要。

    假设三级缓存没有,也就是说提前曝光的时候直接执行完函数式接口返回对象放入二级缓存中。个人感觉是没有任何问题的。

    综上所述,可以二级,也就是singletonObjects和earlySingletonObjects得存在。

    那么既然两级可以解决,为什么spring使用得三级缓存,个人认为三级是可以提高效率的。并不是所有的bean都存在循环依赖,也就是说提前曝光的ObjectFactory也不是都需要执行,这么做可能(猜测)是为了提高效率。

    说到最后

    当两个或多个Bean之间的构造函数形成循环依赖时,Spring无法确定哪个Bean应该先创建,因为每个Bean的创建都依赖于其他Bean。这种情况下,Spring无法通过构造函数注入解决循环依赖,从而导致异常的抛出。

    org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘testB’ defined in file [G:\qhyu-spring\spring-framework\spring-qhyu\build\classes\java\main\com\qhyu\cloud\circlarRefrence\TestB.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘testA’: Requested bean is currently in creation: Is there an unresolvable circular reference?

    @Service
    public class TestA {
    
    	public TestA(TestB testB) {
    		this.testB = testB;
    	}
    
    	TestB testB;
    }
    @Service
    public class TestB {
    	public TestB(TestA testA) {
    		this.testA = testA;
    	}
    
    	TestA testA;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Spring@Lazy是如何解决构造函数循环依赖问题

  • 相关阅读:
    vscode带命令行参数进行调试Golang go-admin:正确配置如下:
    【新材料新能源行业可用】PFA容量瓶定容标准区分
    C#毕业设计——基于C#+asp.net+sqlserver的学生信息管理系统设计与实现(毕业论文+程序源码)——学生信息管理系统
    rsa加密解密java和C#互通
    Tensorflow Bug :got shape [1, 7], but wanted [1].
    非侵入式负荷检测与分解:电力数据挖掘新视角
    【公众号备份】运维现状思考之字字珠玑
    SharePoint 非365版本接入简要笔记
    欧美风商务简约通用PPT模板
    cpu_relax和_mm_pause
  • 原文地址:https://blog.csdn.net/Tanganling/article/details/134003824