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对象信息了。

相关推荐
gb421528736 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶37 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
颜淡慕潇1 小时前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-9 小时前
验证码机制
前端·后端
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭10 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
超爱吃士力架10 小时前
邀请逻辑
java·linux·后端
李小白6610 小时前
Spring MVC(上)
java·spring·mvc
AskHarries12 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
Lojarro13 小时前
【Spring】Spring框架之-AOP
java·mysql·spring