Spring - 循环依赖

Spring - 循环依赖


1. 循环依赖是什么?

循环依赖,顾名思义,指的是两个或者多个Bean之间相互依赖,形成了一个闭环。在Java代码中,循环依赖通常如下形式:

java 复制代码
public class A {
    private B b;
    // get set 方法
}

public class B {
    private A a;
    // get set 方法
}

在这个例子中,类A依赖于类B,而类B也依赖于类A,形成了一个依赖的闭环,这就是所谓的循环依赖。

2. 什么情况下循环依赖可以被处理?

💡 先阅读 3. Spring循环依赖源码分析 在思考什么情况下 `****循环依赖可以被处理****`

在使用Setting方法进行属性注入时,Spring可以处理 循环依赖 问题。

如果使用构造方法进行属性注入,有概率会出现不能处理的情况。

先加载的Bean属性,是通过Setting进行属性注入的,那循环依赖就可以被处理。

3. Spring循环依赖源码分析

在开始分析Spring解决循环依赖问题前我们不如先思考思考,一般在业务系统中我们是怎么样实现数据缓存的。
💡 大致实现流程 → 获取时, 先判断在缓存中是否存在内容信息,存在就直接进行返回。缓存中不存在时,先获取想要的内容后,再设置到缓存中。

为什么要提这个?因为Spring解决依赖源码就是通过缓存的方式来实现的。
💡 先简单回顾一下 Bean 对象的创建过程

  1. 实例化,对应方法:**AbstractAutowireCapableBeanFactory中的 createBeanInstance**方法
  2. 属性注入,对应方法:**AbstractAutowireCapableBeanFactory populateBean**方法
  3. 初始化,对应方法:AbstractAutowireCapableBeanFactory initializeBean

doGetBean方法

在创建Bean时, 最终都指向 doGetBean 方法来获取到Bean对象。

那既然是缓存,那肯定在这个地方补充最适合不过了,代码中的 getSingleton() 就是Spring对于单利Bean的缓存实现了

既然是缓存的时候,那在开始之前, 先简单介绍下 Spring中的一级、二级、三级缓存中分别都存储了那些东西:

  1. **一级缓存(singletonObjects)**存储已经创建完成的单例Bean信息
  2. **二级缓存(earlySingletonObjects)**存储通过三级缓存(singletonFactories),获取到的Bean对象信息。
  3. **三级缓存(singletonFactories)**存储实例化成功的Bean,通过 ObjectFactory 可以获取到Bean实例化对象。在AOP时, 会一个AOP的代理对象,而不用原始的Bean实例化对象了。

💡 看完以后感觉很懵逼,二级缓存和三级缓存在某种程度上其实可以理解为是一个东西。

这个时候小明发出的疑问:

markdown 复制代码
    那为什么不直接在实例化bean以后就支持设置到二级缓存中呢?哪怕是AOP其实也可以拿着时候预先创建好代理对象,并设置到二级缓存中,完全不需要使用3级缓存。

针对小明的疑问发出惊呼, 说的好。

先通过一个简单的执行流程图,看看在 getSingleton 方法中到底做了哪些事情。

  1. 先通过BeanName,判断一级缓存(singletonObjects)中是否已经存在Bean实例。一级缓存中存储的都是已经创建完成的Bean单例对象,存在时直接返回即可。
  2. 在二级、三级缓存时,需要先判断当前Bean是不是属于正在创建中的实例对象。二级、三级缓存主要是为了处理循环依赖而设计。存储的是已经实例化成功的Bean,但是还没有完成属性注入。先存储这些Bean实例对象,在循环依赖发生时可以提前注入。
  3. 先通过三级缓存可以获取到Bean实例对象,再设置到二级缓存中,避免三级缓存重复创建。
graph LR beanName--->singletonObjects(一级缓存) singletonObjects--->bean{存在Bean&正在创建} bean-->|存在| result[结果] bean-->|不存在| earlySingletonObjects(二级缓存) earlySingletonObjects-->earlySingletonObjectsIsNull{是否为空} earlySingletonObjectsIsNull-->|not null| result[结果] earlySingletonObjectsIsNull-->|null| earlyBean{开启三级缓存} earlyBean-->|不开启| result[结果] earlyBean -->|开启| singletonFactory(三级缓存) singletonFactory--singletonObject获取--->singletonBean(三级缓存获取实例) singletonBean-->singletonBeanNotNull{是否为空} singletonBeanNotNull-->|null| result[结果] singletonBeanNotNull-->|not null| actionSingletonBean[三级缓存获取实例] actionSingletonBean--1添加到二级缓存--->earlySingletonObjects actionSingletonBean--2删除三级缓存--->singletonFactory actionSingletonBean--3返回结果对象--->result[结果]

在大致了解了缓存判断逻辑后。在来阅读源码就比较简单了,源码如下:

java 复制代码
// 获取缓存信息
// beanName bean的名称
// allowEarlyReference 是否启用三级缓存, 只有在 `doGetBean` 也就刚获取Bean时才会为true. 其他场景下都是 false
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 一级缓存 (singletonObjects) : 已经创建完成的单例 Bean 对象
		Object singletonObject = this.singletonObjects.get(beanName);
		// 当一级缓存不存在的时候, 有两个场景
    // 1. Bean还未进行创建, 这个就没有在考虑二、三级缓存了
    // 2. Bean已经在创建了, 但是还未完全创建成功(填充字段属性阶段)这个阶段的Bean会存在循环依赖的可能
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 二级缓存 (earlySingletonObjects) : Bean实例对象,介入AOP切面的Bean存储的是代理对象
			singletonObject = this.earlySingletonObjects.get(beanName);
			// 三级缓存 (singletonFactories) : 主要是针对AOP的扩展, 如果被AOP切面则会返回代理对象
			// 可以理解为 Bean 实例化后的对象
			if (singletonObject == null && allowEarlyReference) {
				synchronized (this.singletonObjects) {
					singletonObject = this.singletonObjects.get(beanName);
					if (singletonObject == null) {
						singletonObject = this.earlySingletonObjects.get(beanName);
						if (singletonObject == null) {
							ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
							if (singletonFactory != null) {
								// 获取三级缓存中存储的Bean实例化对象, 并设置到二级缓存中, 编辑三级缓存重复调用
								singletonObject = singletonFactory.getObject();
								this.earlySingletonObjects.put(beanName, singletonObject);
								this.singletonFactories.remove(beanName);
							}
						}
					}
				}
			}
		}
		return singletonObject;
	}

通过上述分析,大致对于对Spring一级、二级、三级缓存及Spring如何处理循环依赖的有了一个较为清晰的认知。那Spring的一级、二级、三级缓存是在什么时候进行设置的呢?
💡 在思考问题前,不妨我们先简单回顾一下Bean的生命周期

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

那 "循环依赖" 问题发生在那个阶段呢?没错是在 "属性赋值" 阶段。在二级、三级缓存的处理必然是在 "属性赋值"之前,"实例化" 之后了。
💡 实例化阶段,通过构造方法注入。在一定场景下会导致Spring循环依赖处理失败。

有了具体的思路以后,我们在看看具体的方法 doCreateBean 方法,是Spring创建单例Bean的实现,代码如下:

java 复制代码
// 单利Bean创建过程
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
		
		// 第一部分: BeanDefinition转化为Bean实例
		BeanWrapper instanceWrapper = null;
		if (mbd.isSingleton()) {
			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
		}
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		// Bean对象信息
		Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		if (beanType != NullBean.class) {
			mbd.resolvedTargetType = beanType;
		}
		// ........ 缺省 .........

		// **是否允许循环依赖, 单利Bean & 允许循环依赖 & 当前Bean正在创建中**
		// **所以的单利Bean都会在这里进行缓存**
		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** 方法中, Spring把创建好的Bean对象, 存储到了三级缓存中
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// ........ 缺省 .........
	}

通过 createBeanInstance 获取到Bean实例后,在允许循环依赖下,所有单例的Bean都会执行 addSingletonFactory 方法,然后在进行后续初始化操作。是的对于三级缓存的实现就是在 addSingletonFactory 方法中。

addSingletonFactory()

addSingletonFactory 方法中,发现SpringBean 实例化对象,都会在 singletonFactories 三级缓存中进行补充。所以重点我们应该观察在 三级缓存 singletonFactories 中到底存储的是什么?

java 复制代码
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);
			}
		}
	}

getEarlyBeanReference()

可以看到在三级缓存中,存储的实际上是一个 函数试接口 内部执行了执行了 SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference() 方法,那在方法中具体是做了哪些事呢?

我个人理解是Spring对Bean实例化后的扩展,可以通过 getEarlyBeanReference 来调整 Bean的实例化 对象。

结合一下场景,一般什么情况下会需要改变 Bean的实例化 ,是的。在引用AOP后,会需要 代理对象 。设想一下,有一个被AOP代理的Bean,如果在 三级缓存 中存储的是自己本身的Bean,而不是其 代理对象 ,那在属性注入的时候,就存在问题了。
💡 具体实现AOP可以看 AbstractAutoProxyCreator.getEarlyBeanReference(),返回了提前创建好的代理对象。

java 复制代码
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

// 非AOP场景下, getEarlyBeanReference其实是不会被执行的, 而是直接返回 Bean 对象信息
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		// if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		//	for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
		//		exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		//	}
		}
		return exposedObject;
	}

到此为止,Spring在处理循环依赖的相关流程,相信也有较为清晰的理解。
💡 个人小总结之Spring处理循环依赖概述: 在Spring中,循环依赖问题一般发生在属性注入的时候,为了解决该问题。Spring会把Bean对象,在实例化后,属性注入之前。先存储到三级缓存当中去。如果真出现循环依赖问题,那也可以从三级缓存中提前获取到Bean对象,进行属性注入,从而避免循环依赖问题。但是在使用 `构造方法` 注入场景下,有概率会出现失效问题。是因为构造方法注入时,Bean还没有完成实例化,三级缓存中也就不会存在该Bean,就引发了注入问题。但是也不是绝对的,比如 A 通过 Setting 方式注入,B 通过构造方法注入,这个时候就不会有问题。因为Spring会先加载A,然后再去加载B,三级缓存中已经存在A对象信息了。

相关推荐
Ai 编码助手2 小时前
在 Go 语言中如何高效地处理集合
开发语言·后端·golang
小丁爱养花2 小时前
Spring MVC:HTTP 请求的参数传递2.0
java·后端·spring
Channing Lewis2 小时前
什么是 Flask 的蓝图(Blueprint)
后端·python·flask
轩辕烨瑾3 小时前
C#语言的区块链
开发语言·后端·golang
feilieren4 小时前
SpringBoot 搭建 SSE
java·spring boot·spring
栗豆包5 小时前
w175基于springboot的图书管理系统的设计与实现
java·spring boot·后端·spring·tomcat
萧若岚6 小时前
Elixir语言的Web开发
开发语言·后端·golang
Channing Lewis6 小时前
flask实现重启后需要重新输入用户名而避免浏览器使用之前已经记录的用户名
后端·python·flask
Channing Lewis6 小时前
如何在 Flask 中实现用户认证?
后端·python·flask
一只爱吃“兔子”的“胡萝卜”7 小时前
2.Spring-AOP
java·后端·spring